Compare commits
No commits in common. "e8d1a8afded1df4a74677884b02414bc5aeceaef" and "d59155387274ebc0eb6caa554fbfd04fd771c2e7" have entirely different histories.
e8d1a8afde
...
d591553872
|
|
@ -20,7 +20,7 @@ import { getWeight, lootboxes } from "../../shared/lootboxes";
|
||||||
import { weightedPickRandom } from "../../shared/utils";
|
import { weightedPickRandom } from "../../shared/utils";
|
||||||
import { emit } from "../events";
|
import { emit } from "../events";
|
||||||
import { Game } from "../schema";
|
import { Game } from "../schema";
|
||||||
import { and, count, eq, gt, max, not, sum } from "drizzle-orm";
|
import { and, eq, gt } from "drizzle-orm";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const secret = process.env.SECRET!;
|
const secret = process.env.SECRET!;
|
||||||
|
|
@ -185,7 +185,11 @@ export const userController = createController({
|
||||||
}),
|
}),
|
||||||
async ({ id }, { db }) => {
|
async ({ id }, { db }) => {
|
||||||
const now = dayjs();
|
const now = dayjs();
|
||||||
const firstOfYear = now.startOf("year");
|
const firstOfYear = now
|
||||||
|
.set("day", 0)
|
||||||
|
.set("month", 0)
|
||||||
|
.set("hour", 0)
|
||||||
|
.set("minute", 0);
|
||||||
const gamesOfUser = await db.query.Game.findMany({
|
const gamesOfUser = await db.query.Game.findMany({
|
||||||
where: and(eq(Game.user, id), gt(Game.finished, firstOfYear.valueOf())),
|
where: and(eq(Game.user, id), gt(Game.finished, firstOfYear.valueOf())),
|
||||||
});
|
});
|
||||||
|
|
@ -199,34 +203,4 @@ export const userController = createController({
|
||||||
return heat;
|
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,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
),
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,6 @@
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
"@radix-ui/react-dropdown-menu": "^2.1.2",
|
||||||
"@radix-ui/react-popover": "^1.1.2",
|
"@radix-ui/react-popover": "^1.1.2",
|
||||||
"@radix-ui/react-switch": "^1.1.1",
|
"@radix-ui/react-switch": "^1.1.1",
|
||||||
"@radix-ui/react-tooltip": "^1.2.7",
|
|
||||||
"@tanstack/react-query": "^5.59.11",
|
"@tanstack/react-query": "^5.59.11",
|
||||||
"@tanstack/react-query-devtools": "^5.59.11",
|
"@tanstack/react-query-devtools": "^5.59.11",
|
||||||
"@tsparticles/engine": "^3.5.0",
|
"@tsparticles/engine": "^3.5.0",
|
||||||
|
|
@ -35,9 +34,9 @@
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"drizzle-orm": "0.33.0",
|
"drizzle-orm": "0.33.0",
|
||||||
|
"framer-motion": "^11.11.8",
|
||||||
"jotai": "^2.10.0",
|
"jotai": "^2.10.0",
|
||||||
"lucide-react": "^0.452.0",
|
"lucide-react": "^0.452.0",
|
||||||
"motion": "^12.18.1",
|
|
||||||
"pixi-viewport": "^5.0.3",
|
"pixi-viewport": "^5.0.3",
|
||||||
"pixi.js": "^7.0.0",
|
"pixi.js": "^7.0.0",
|
||||||
"pixi.js-legacy": "^7.4.2",
|
"pixi.js-legacy": "^7.4.2",
|
||||||
|
|
@ -69,7 +68,6 @@
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.6.3",
|
||||||
"typescript-eslint": "^8.8.1",
|
"typescript-eslint": "^8.8.1",
|
||||||
"vite": "^5.4.8",
|
"vite": "^5.4.8",
|
||||||
"vite-bundle-analyzer": "^0.22.3",
|
|
||||||
"vite-imagetools": "^7.0.4"
|
"vite-imagetools": "^7.0.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { type PropsWithChildren, useEffect, useRef, useState } from "react";
|
import { type PropsWithChildren, useEffect, useRef, useState } from "react";
|
||||||
import { Button } from "./components/Button";
|
import { Button } from "./components/Button";
|
||||||
import { motion } from "motion/react";
|
import { motion } from "framer-motion";
|
||||||
import {
|
import {
|
||||||
GitBranch,
|
GitBranch,
|
||||||
History,
|
History,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ import {
|
||||||
type LoadedTexture,
|
type LoadedTexture,
|
||||||
type LoadedTheme,
|
type LoadedTheme,
|
||||||
type Theme,
|
type Theme,
|
||||||
|
useTheme,
|
||||||
} from "../themes/Theme";
|
} from "../themes/Theme";
|
||||||
import { useTheme } from "../themes/useTheme";
|
|
||||||
import { Container, Sprite, Stage, useTick } from "@pixi/react";
|
import { Container, Sprite, Stage, useTick } from "@pixi/react";
|
||||||
import Viewport from "./pixi/PixiViewport";
|
import Viewport from "./pixi/PixiViewport";
|
||||||
import type { Viewport as PixiViewport } from "pixi-viewport";
|
import type { Viewport as PixiViewport } from "pixi-viewport";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { animate, motion } from "motion/react";
|
import { animate, motion } from "framer-motion";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
|
|
||||||
const BounceImg = ({ src, className }: { src: string; className?: string }) => {
|
const BounceImg = ({ src, className }: { src: string; className?: string }) => {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { AnimatePresence, motion } from "motion/react";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { feedItemsAtom, lootboxResultAtom } from "../../atoms";
|
import { feedItemsAtom, lootboxResultAtom } from "../../atoms";
|
||||||
import FeedItemElement from "./FeedItem";
|
import FeedItemElement from "./FeedItem";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { motion } from "motion/react";
|
import { motion } from "framer-motion";
|
||||||
import type { PropsWithChildren } from "react";
|
import type { PropsWithChildren } from "react";
|
||||||
import { formatTimeSpan } from "../../../shared/time";
|
import { formatTimeSpan } from "../../../shared/time";
|
||||||
import GemsIcon from "../GemIcon";
|
import GemsIcon from "../GemIcon";
|
||||||
|
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import { lazy } from "react";
|
|
||||||
|
|
||||||
export const Board = lazy(() => import("./Board"));
|
|
||||||
|
|
@ -9,8 +9,8 @@ interface PastMatchProps {
|
||||||
|
|
||||||
const PastMatch = ({ game }: PastMatchProps) => {
|
const PastMatch = ({ game }: PastMatchProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4 items-center w-full @container">
|
<div className="flex flex-col gap-4 items-center w-full">
|
||||||
<div className="w-full bg-white/10 border-white/20 border-1 rounded-md grid justify-center grid-cols-4 @max-xl:grid-cols-3 p-4">
|
<div className="w-full bg-white/10 border-white/20 border-1 rounded-md grid justify-center grid-cols-4 p-4">
|
||||||
<div className="flex-col flex">
|
<div className="flex-col flex">
|
||||||
<div className="text-white/90 text-lg">Endless</div>
|
<div className="text-white/90 text-lg">Endless</div>
|
||||||
<div className="text-white/50 text-lg">
|
<div className="text-white/50 text-lg">
|
||||||
|
|
@ -24,7 +24,7 @@ const PastMatch = ({ game }: PastMatchProps) => {
|
||||||
{game.minesCount - game.isFlagged.flat().filter((f) => f).length}
|
{game.minesCount - game.isFlagged.flat().filter((f) => f).length}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-white/80 text-lg @max-xl:hidden">
|
<div className="text-white/80 text-lg">
|
||||||
<div>Duration: {formatTimeSpan(game.finished - game.started)}</div>
|
<div>Duration: {formatTimeSpan(game.finished - game.started)}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-end">
|
<div className="flex justify-end">
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
"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<typeof TooltipPrimitive.Provider>) {
|
|
||||||
return (
|
|
||||||
<TooltipPrimitive.Provider
|
|
||||||
data-slot="tooltip-provider"
|
|
||||||
delayDuration={delayDuration}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Tooltip({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Root>) {
|
|
||||||
return (
|
|
||||||
<TooltipProvider>
|
|
||||||
<TooltipPrimitive.Root data-slot="tooltip" {...props} />
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function TooltipTrigger({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
|
|
||||||
return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
function TooltipContent({
|
|
||||||
className,
|
|
||||||
sideOffset = 0,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TooltipPrimitive.Content>) {
|
|
||||||
return (
|
|
||||||
<TooltipPrimitive.Portal>
|
|
||||||
<TooltipPrimitive.Content
|
|
||||||
data-slot="tooltip-content"
|
|
||||||
sideOffset={sideOffset}
|
|
||||||
className={cn(
|
|
||||||
"bg-primary text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-md px-3 py-1.5 text-xs text-balance",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<TooltipPrimitive.Arrow className="bg-primary fill-primary z-50 size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px]" />
|
|
||||||
</TooltipPrimitive.Content>
|
|
||||||
</TooltipPrimitive.Portal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
|
|
||||||
|
|
@ -1,36 +1,20 @@
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--color-primary: #d9afd9;
|
--color-primary: #D9AFD9;
|
||||||
--color-input: color-mix(in srgb, var(--color-white, #fff) 20%, transparent);
|
--color-input: color-mix(in srgb, var(--color-white, #fff) 20%, transparent);
|
||||||
--color-background: black;
|
--color-background: black;
|
||||||
--color-common: var(--color-sky-500);
|
--color-common: var(--color-sky-500);
|
||||||
--color-uncommon: var(--color-green-400);
|
--color-uncommon: var(--color-green-400);
|
||||||
--color-rare: var(--color-red-500);
|
--color-rare: var(--color-red-500);
|
||||||
--color-legendary: var(--color-amber-500);
|
--color-legendary: var(--color-amber-500);
|
||||||
--bg-brand: -webkit-linear-gradient(
|
--bg-brand: -webkit-linear-gradient(225deg, rgb(251, 175, 21), rgb(251, 21, 242),
|
||||||
225deg,
|
rgb(21, 198, 251)) 0% 0% / 100% 300%;
|
||||||
rgb(251, 175, 21),
|
--bg-secondary: linear-gradient(90deg, #D9AFD9 0%, #97D9E1 100%) 0% 0% / 100% 300%;
|
||||||
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;
|
--animate-gradientmove: gradientmove 1s ease 0s 1 normal forwards;
|
||||||
@keyframes gradientmove {
|
@keyframes gradientmove {
|
||||||
0% {
|
0%{background-position: 0% 0%}
|
||||||
background-position: 0% 0%;
|
100%{background-position: 0% 100%}
|
||||||
}
|
|
||||||
100% {
|
|
||||||
background-position: 0% 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer components {
|
|
||||||
.bg-brand {
|
|
||||||
background: var(--bg-brand);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +22,7 @@ button {
|
||||||
cursor: pointer;
|
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;
|
@apply border-b border-white/10;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ import Home from "./views/home/Home.tsx";
|
||||||
import Settings from "./views/settings/Settings.tsx";
|
import Settings from "./views/settings/Settings.tsx";
|
||||||
import MatchHistory from "./views/match-history/MatchHistory.tsx";
|
import MatchHistory from "./views/match-history/MatchHistory.tsx";
|
||||||
import Collection from "./views/collection/Collection.tsx";
|
import Collection from "./views/collection/Collection.tsx";
|
||||||
import { AnimatePresence } from "motion/react";
|
import { AnimatePresence } from "framer-motion";
|
||||||
import Store from "./views/store/Store.tsx";
|
import Store from "./views/store/Store.tsx";
|
||||||
import Profile from "./views/profile/Profile.tsx";
|
import Profile from "./views/profile/Profile.tsx";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import type { Texture } from "pixi.js";
|
import { Assets, Texture } from "pixi.js";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
type Png = typeof import("*.png");
|
type Png = typeof import("*.png");
|
||||||
type LazySprite = () => Promise<Png>;
|
type LazySprite = () => Promise<Png>;
|
||||||
|
|
||||||
export interface WeightedLazySprites {
|
interface WeightedLazySprites {
|
||||||
weight: number;
|
weight: number;
|
||||||
sprite: LazySprite;
|
sprite: LazySprite;
|
||||||
}
|
}
|
||||||
|
|
@ -53,3 +54,36 @@ export const mainWithSpecials = (
|
||||||
...specials.map((sprite) => ({ weight: 0.05, sprite })),
|
...specials.map((sprite) => ({ weight: 0.05, sprite })),
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useTheme = (theme: Theme) => {
|
||||||
|
const [loadedTheme, setLoadedTheme] = useState<LoadedTheme | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
useEffect(() => {
|
||||||
|
const loadTheme = async () => {
|
||||||
|
const loadedEntries = await Promise.all(
|
||||||
|
Object.entries(theme).map(async ([key, value]) => {
|
||||||
|
let loaded = value;
|
||||||
|
if (typeof value === "function") {
|
||||||
|
loaded = await Assets.load((await value()).default);
|
||||||
|
}
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
loaded = await Promise.all(
|
||||||
|
loaded.map(async (sprite: WeightedLazySprites) => {
|
||||||
|
return {
|
||||||
|
weight: sprite.weight,
|
||||||
|
sprite: await Assets.load((await sprite.sprite()).default),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [key, loaded] as const;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
setLoadedTheme(Object.fromEntries(loadedEntries) as LoadedTheme);
|
||||||
|
};
|
||||||
|
loadTheme();
|
||||||
|
}, [theme]);
|
||||||
|
return loadedTheme;
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,36 +0,0 @@
|
||||||
import { Assets } from "pixi.js";
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import type { Theme, LoadedTheme, WeightedLazySprites } from "./Theme";
|
|
||||||
|
|
||||||
export const useTheme = (theme: Theme) => {
|
|
||||||
const [loadedTheme, setLoadedTheme] = useState<LoadedTheme | undefined>(
|
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
useEffect(() => {
|
|
||||||
const loadTheme = async () => {
|
|
||||||
const loadedEntries = await Promise.all(
|
|
||||||
Object.entries(theme).map(async ([key, value]) => {
|
|
||||||
let loaded = value;
|
|
||||||
if (typeof value === "function") {
|
|
||||||
loaded = await Assets.load((await value()).default);
|
|
||||||
}
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
loaded = await Promise.all(
|
|
||||||
loaded.map(async (sprite: WeightedLazySprites) => {
|
|
||||||
return {
|
|
||||||
weight: sprite.weight,
|
|
||||||
sprite: await Assets.load((await sprite.sprite()).default),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return [key, loaded] as const;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
setLoadedTheme(Object.fromEntries(loadedEntries) as LoadedTheme);
|
|
||||||
};
|
|
||||||
loadTheme();
|
|
||||||
}, [theme]);
|
|
||||||
return loadedTheme;
|
|
||||||
};
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { Ellipsis } from "lucide-react";
|
import { Ellipsis } from "lucide-react";
|
||||||
import { testBoard } from "../../../shared/testBoard";
|
import { testBoard } from "../../../shared/testBoard";
|
||||||
import { Board } from "../../components/LazyBoard";
|
import Board from "../../components/Board";
|
||||||
import { Button } from "../../components/Button";
|
import { Button } from "../../components/Button";
|
||||||
import { themes } from "../../themes";
|
import { themes } from "../../themes";
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
|
import Board from "../../components/Board";
|
||||||
import { useWSMutation, useWSQuery } from "../../hooks";
|
import { useWSMutation, useWSQuery } from "../../hooks";
|
||||||
import { useAtom } from "jotai";
|
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, useEffect } from "react";
|
||||||
import { Board } from "../../components/LazyBoard";
|
|
||||||
|
|
||||||
interface EndlessProps {
|
interface EndlessProps {
|
||||||
gameId?: string;
|
gameId?: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { animate, motion, useMotionValue, useTransform } from "motion/react";
|
import { animate, motion, useMotionValue, useTransform } from "framer-motion";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useWSQuery } from "../../hooks";
|
import { useWSQuery } from "../../hooks";
|
||||||
import { Tag } from "../../components/Tag";
|
import { Tag } from "../../components/Tag";
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import {
|
||||||
useMotionTemplate,
|
useMotionTemplate,
|
||||||
useScroll,
|
useScroll,
|
||||||
useTransform,
|
useTransform,
|
||||||
} from "motion/react";
|
} from "framer-motion";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { cn } from "../../lib/utils";
|
import { cn } from "../../lib/utils";
|
||||||
|
|
||||||
|
|
@ -48,10 +48,8 @@ const Section = ({ text, image, left }: SectionProps) => {
|
||||||
className="md:w-[50%] h-90"
|
className="md:w-[50%] h-90"
|
||||||
// float up and down
|
// float up and down
|
||||||
animate={{
|
animate={{
|
||||||
// translate: ["0 0", "5 10", "0 0"],
|
translateY: [0, 10, 0],
|
||||||
// transform: ["translate"]
|
translateX: [0, 5, 0],
|
||||||
x: [0, 10, 0],
|
|
||||||
y: [0, 5, 0],
|
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
repeat: Infinity,
|
repeat: Infinity,
|
||||||
|
|
@ -73,7 +71,7 @@ const Section = ({ text, image, left }: SectionProps) => {
|
||||||
translateY,
|
translateY,
|
||||||
}}
|
}}
|
||||||
transition={{
|
transition={{
|
||||||
type: "spring",
|
type: "just",
|
||||||
delay: 0.5,
|
delay: 0.5,
|
||||||
}}
|
}}
|
||||||
srcSet={image.map((i) => `${i.src} ${i.width}w`).join(", ")}
|
srcSet={image.map((i) => `${i.src} ${i.width}w`).join(", ")}
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,35 @@
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { useWSQuery } from "../../hooks";
|
import { useWSQuery } from "../../hooks";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import {
|
|
||||||
Tooltip,
|
|
||||||
TooltipContent,
|
|
||||||
TooltipTrigger,
|
|
||||||
} from "../../components/Tooltip";
|
|
||||||
import PastMatch from "../../components/PastMatch";
|
|
||||||
|
|
||||||
const Profile: React.FC = () => {
|
const Profile: React.FC = () => {
|
||||||
const { data: username } = useWSQuery("user.getSelf", null);
|
const { data: heatmap } = useWSQuery("user.getHeatmap", { id: "Gordon" });
|
||||||
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 now = useMemo(() => dayjs(), []);
|
||||||
const firstOfYear = useMemo(() => now.startOf("year"), [now]);
|
const firstOfYear = useMemo(
|
||||||
|
() => now.set("day", 0).set("month", 0).set("hour", 0).set("minute", 0),
|
||||||
|
[now],
|
||||||
|
);
|
||||||
const weeks = now.diff(firstOfYear, "weeks") + 1;
|
const weeks = now.diff(firstOfYear, "weeks") + 1;
|
||||||
const maxHeat = heatmap ? Math.max(...heatmap) : 0;
|
const maxHeat = heatmap ? Math.max(...heatmap) : 0;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid md:[grid-template-columns:_2fr_3fr] gap-6">
|
<div>
|
||||||
<div className="m-8 text-white flex self-center">
|
|
||||||
<div className="p-2 flex items-center text-2xl">{username}</div>
|
|
||||||
<div className="border-l-white border-l p-2 text-lg">
|
|
||||||
<p>Total Games: {profile?.totalGames}</p>
|
|
||||||
<p>Highest Stage: {profile?.highestStage}</p>
|
|
||||||
<p>
|
|
||||||
Average Stage: {Math.round(profile?.averageStage ?? 1 * 100) / 100}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col gap-4">
|
|
||||||
{pastGames?.data
|
|
||||||
.slice(0, 4)
|
|
||||||
.map((game) => <PastMatch key={game.uuid} game={game} />)}
|
|
||||||
</div>
|
|
||||||
{heatmap && (
|
{heatmap && (
|
||||||
<div className="col-span-full">
|
<div className="flex gap-2">
|
||||||
<h2 className="text-white text-2xl font-semibold mb-4">Activity</h2>
|
|
||||||
<div className="flex gap-2 ">
|
|
||||||
{Array.from({ length: weeks }).map((_, w) => (
|
{Array.from({ length: weeks }).map((_, w) => (
|
||||||
<div key={w} className="w-6 flex gap-2 flex-col">
|
<div key={w} className="w-4 flex gap-2 flex-col">
|
||||||
{Array.from({ length: 7 }).map((_, d) => {
|
{Array.from({ length: 7 }).map((_, d) => (
|
||||||
const index = w * 7 + d;
|
<div key={d} className="w-4 h-4 border border-white">
|
||||||
if (index >= heatmap.length) return;
|
|
||||||
return (
|
|
||||||
<Tooltip key={d}>
|
|
||||||
<TooltipTrigger>
|
|
||||||
<div className="w-5 h-5 border border-white">
|
|
||||||
<div
|
<div
|
||||||
className="w-5 h-5 bg-brand -m-px"
|
className="w-4 h-4 bg-purple-600 -m-px"
|
||||||
style={{
|
style={{
|
||||||
opacity: heatmap[index] / maxHeat,
|
opacity: heatmap[w * 7 + d] / maxHeat,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipContent>
|
|
||||||
<p>
|
|
||||||
{firstOfYear
|
|
||||||
.clone()
|
|
||||||
.add(index, "days")
|
|
||||||
.format("DD/MM/YYYY")}
|
|
||||||
</p>
|
|
||||||
<p>{heatmap[index]} Games Played</p>
|
|
||||||
</TooltipContent>
|
|
||||||
</Tooltip>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</div>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,9 @@ import { lootboxResultAtom } from "../../atoms";
|
||||||
import { useAtom } from "jotai";
|
import { useAtom } from "jotai";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import Particles, { initParticlesEngine } from "@tsparticles/react";
|
import Particles, { initParticlesEngine } from "@tsparticles/react";
|
||||||
import { motion } from "motion/react";
|
import { loadSlim } from "@tsparticles/slim";
|
||||||
|
import { loadSeaAnemonePreset } from "@tsparticles/preset-sea-anemone";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import BounceImg from "../../components/BounceImg";
|
import BounceImg from "../../components/BounceImg";
|
||||||
|
|
||||||
const Store = () => {
|
const Store = () => {
|
||||||
|
|
@ -27,11 +29,6 @@ const Store = () => {
|
||||||
|
|
||||||
// this should be run only once per application lifetime
|
// this should be run only once per application lifetime
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cb = async () => {
|
|
||||||
const { loadSlim } = await import("@tsparticles/slim");
|
|
||||||
const { loadSeaAnemonePreset } = await import(
|
|
||||||
"@tsparticles/preset-sea-anemone"
|
|
||||||
);
|
|
||||||
initParticlesEngine(async (engine) => {
|
initParticlesEngine(async (engine) => {
|
||||||
// you can initiate the tsParticles instance (engine) here, adding custom shapes or presets
|
// you can initiate the tsParticles instance (engine) here, adding custom shapes or presets
|
||||||
// this loads the tsparticles package bundle, it's the easiest method for getting everything ready
|
// this loads the tsparticles package bundle, it's the easiest method for getting everything ready
|
||||||
|
|
@ -43,8 +40,6 @@ const Store = () => {
|
||||||
|
|
||||||
//await loadBasic(engine);
|
//await loadBasic(engine);
|
||||||
});
|
});
|
||||||
};
|
|
||||||
cb();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,11 @@ 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";
|
|
||||||
|
|
||||||
// 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()],
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue