Fix CI build error by updating tailwindcss and @tailwindcss/vite
The build was failing with 'TypeError: Cannot convert undefined or null to object' in @tailwindcss/vite:generate:build. Updating to version 4.2.4 resolves this. Co-authored-by: MasterGordon <18127395+MasterGordon@users.noreply.github.com>
This commit is contained in:
parent
676bd7a978
commit
83d23c77b1
|
|
@ -51,7 +51,7 @@
|
|||
"zod": "^4.1.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/vite": "next",
|
||||
"@tailwindcss/vite": "^4.2.4",
|
||||
"@types/bun": "latest",
|
||||
"@types/random-seed": "^0.3.5",
|
||||
"@types/react": "^19.1.13",
|
||||
|
|
@ -59,7 +59,7 @@
|
|||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"drizzle-kit": "0.31.4",
|
||||
"oxlint": "^1.51.0",
|
||||
"tailwindcss": "^4.1.13",
|
||||
"tailwindcss": "^4.2.4",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^8.0.0-beta.14",
|
||||
"vite-bundle-analyzer": "^1.2.3",
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ import { useMediaQuery } from "@uidotdev/usehooks";
|
|||
import Header from "./components/Header";
|
||||
import { Tag } from "./components/Tag";
|
||||
import Feed from "./components/Feed/Feed";
|
||||
import { useAtom } from "jotai";
|
||||
import { loginTokenAtom } from "./atoms";
|
||||
import { useWSQuery } from "./hooks";
|
||||
|
||||
const drawerWidth = 256;
|
||||
const drawerWidthWithPadding = drawerWidth;
|
||||
|
|
@ -26,7 +25,7 @@ const drawerWidthWithPadding = drawerWidth;
|
|||
const Shell: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const drawerRef = useRef<HTMLDivElement>(null);
|
||||
const [loginToken] = useAtom(loginTokenAtom);
|
||||
const { data: username } = useWSQuery("user.getSelf", null);
|
||||
|
||||
const x = isOpen ? 0 : -drawerWidthWithPadding;
|
||||
const width = isOpen ? drawerWidthWithPadding : 0;
|
||||
|
|
@ -82,7 +81,7 @@ const Shell: React.FC<PropsWithChildren> = ({ children }) => {
|
|||
<Play />
|
||||
Play
|
||||
</NavLink>
|
||||
{loginToken && (
|
||||
{username && (
|
||||
<NavLink href="/history">
|
||||
<History />
|
||||
History
|
||||
|
|
@ -92,13 +91,13 @@ const Shell: React.FC<PropsWithChildren> = ({ children }) => {
|
|||
<Store />
|
||||
Store
|
||||
</NavLink>
|
||||
{loginToken && (
|
||||
{username && (
|
||||
<NavLink href="/collection">
|
||||
<Library />
|
||||
Collection <Tag size="sm">NEW</Tag>
|
||||
</NavLink>
|
||||
)}
|
||||
{loginToken && (
|
||||
{username && (
|
||||
<NavLink href="/settings">
|
||||
<Settings />
|
||||
Settings
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { useQueryClient } from "@tanstack/react-query";
|
|||
import PasswordInput from "./PasswordInput";
|
||||
import { wsClient } from "../../wsClient";
|
||||
|
||||
const RegisterButton = () => {
|
||||
const RegisterButton = ({ label }: { label?: string }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isLoginMode, setIsLoginMode] = useState(false);
|
||||
const [username, setUsername] = useState("");
|
||||
|
|
@ -36,7 +36,7 @@ const RegisterButton = () => {
|
|||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="primary" className="self-start">
|
||||
Register
|
||||
{label ?? "Register"}
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
|
|
|
|||
104
src/index.css
104
src/index.css
|
|
@ -41,107 +41,3 @@ button {
|
|||
.grid-border-b div:not(:nth-last-child(-n + 3)) {
|
||||
@apply border-b border-white/10;
|
||||
}
|
||||
|
||||
/* .game-board { */
|
||||
/* display: grid; */
|
||||
/* gap: 2px; */
|
||||
/* max-width: fit-content; */
|
||||
/* } */
|
||||
/**/
|
||||
/* .game-wrapper { */
|
||||
/* display: flex; */
|
||||
/* flex-direction: column; */
|
||||
/* align-items: center; */
|
||||
/* } */
|
||||
/**/
|
||||
/* .mine-button { */
|
||||
/* background-color: #666; */
|
||||
/* border: 1px solid black; */
|
||||
/* width: 2rem; */
|
||||
/* height: 2rem; */
|
||||
/* font-size: 1.25rem; */
|
||||
/* user-select: none; */
|
||||
/* display: flex; */
|
||||
/* justify-content: center; */
|
||||
/* align-items: center; */
|
||||
/* font-weight: bold; */
|
||||
/* font-family: monospace; */
|
||||
/* box-sizing: border-box; */
|
||||
/* transition: all 0.2s ease-in-out; */
|
||||
/* } */
|
||||
/**/
|
||||
/* html { */
|
||||
/* background: #111; */
|
||||
/* color: #eee; */
|
||||
/* } */
|
||||
/**/
|
||||
/* body { */
|
||||
/* margin: auto; */
|
||||
/* max-width: 1400px; */
|
||||
/* padding: 1rem; */
|
||||
/* font-family: monospace; */
|
||||
/* } */
|
||||
/**/
|
||||
/* .timer { */
|
||||
/* flex-grow: 1; */
|
||||
/* display: flex; */
|
||||
/* justify-content: space-between; */
|
||||
/* align-items: center; */
|
||||
/* font-size: 2rem; */
|
||||
/* font-family: monospace; */
|
||||
/* } */
|
||||
/**/
|
||||
/* .footer { */
|
||||
/* display: flex; */
|
||||
/* flex-direction: column; */
|
||||
/* align-items: center; */
|
||||
/* font-size: 1rem; */
|
||||
/* font-family: monospace; */
|
||||
/* } */
|
||||
/**/
|
||||
/* pre { */
|
||||
/* margin: 0; */
|
||||
/* } */
|
||||
/**/
|
||||
/* .stage { */
|
||||
/* font-size: 1rem; */
|
||||
/* font-family: monospace; */
|
||||
/* } */
|
||||
/**/
|
||||
/* input { */
|
||||
/* font-size: 14px; */
|
||||
/* margin: 12px; */
|
||||
/* padding: 6px 12px 6px 12px; */
|
||||
/* border-radius: 0.7em; */
|
||||
/* background: #333; */
|
||||
/* color: #eee; */
|
||||
/* border: 1px solid rgb(251, 21, 242); */
|
||||
/**/
|
||||
/* } */
|
||||
/**/
|
||||
/* button { */
|
||||
/* color: white; */
|
||||
/* font-weight: 600; */
|
||||
/* font-size: 14px; */
|
||||
/* margin: 12px; */
|
||||
/* padding: 6px 12px 6px 12px; */
|
||||
/* border-radius: 0.7em; */
|
||||
/* background: -webkit-linear-gradient(225deg, rgb(251, 175, 21), rgb(251, 21, 242), */
|
||||
/* rgb(21, 198, 251)) 0% 0% / 300% 300%; */
|
||||
/* background-size: 200% auto; */
|
||||
/* } */
|
||||
/**/
|
||||
/* button:hover { */
|
||||
/* animation: gradient_move 1s ease infinite; */
|
||||
/* } */
|
||||
/**/
|
||||
/**/
|
||||
/* .header { */
|
||||
/* display: grid; */
|
||||
/* grid-template-columns: 1fr 1fr; */
|
||||
/* margin-bottom: 1rem; */
|
||||
/* } */
|
||||
/**/
|
||||
/* .scores { */
|
||||
/* text-align: right; */
|
||||
/* } */
|
||||
|
|
|
|||
40
src/main.tsx
40
src/main.tsx
|
|
@ -15,17 +15,49 @@ import Collection from "./views/collection/Collection.tsx";
|
|||
import { AnimatePresence } from "motion/react";
|
||||
import Store from "./views/store/Store.tsx";
|
||||
import Profile from "./views/profile/Profile.tsx";
|
||||
import { useWSQuery } from "./hooks.ts";
|
||||
import RegisterButton from "./components/Auth/RegisterButton.tsx";
|
||||
|
||||
const ProtectedRoute: React.FC<{
|
||||
component: React.ComponentType<any>;
|
||||
path: string;
|
||||
}> = ({ component: Component, path }) => {
|
||||
const { data: username, isLoading } = useWSQuery("user.getSelf", null);
|
||||
|
||||
return (
|
||||
<Route path={path}>
|
||||
{(params) => {
|
||||
if (isLoading) return null;
|
||||
if (!username) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center py-24 gap-6">
|
||||
<h2 className="text-white/90 text-2xl font-bold">
|
||||
This page is only available to logged-in users.
|
||||
</h2>
|
||||
<RegisterButton label="Login to access" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return <Component {...params} />;
|
||||
}}
|
||||
</Route>
|
||||
);
|
||||
};
|
||||
|
||||
const setup = async () => {
|
||||
const token = localStorage.getItem("loginToken");
|
||||
|
||||
if (token) {
|
||||
try {
|
||||
await wsClient.dispatch("user.loginWithToken", {
|
||||
const res = await wsClient.dispatch("user.loginWithToken", {
|
||||
token: JSON.parse(token),
|
||||
});
|
||||
if (!res.success) {
|
||||
localStorage.removeItem("loginToken");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
localStorage.removeItem("loginToken");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
@ -41,9 +73,9 @@ setup().then(() => {
|
|||
<Route path="/play/:gameId?">
|
||||
{(params) => <Endless gameId={params.gameId} />}
|
||||
</Route>
|
||||
<Route path="/history" component={MatchHistory} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/collection" component={Collection} />
|
||||
<ProtectedRoute path="/history" component={MatchHistory} />
|
||||
<ProtectedRoute path="/settings" component={Settings} />
|
||||
<ProtectedRoute path="/collection" component={Collection} />
|
||||
<Route path="/store" component={Store} />
|
||||
<Route path="/profile/:username?">
|
||||
{(params) => <Profile username={params.username} />}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { useWSMutation, useWSQuery } from "../../hooks";
|
||||
import { useAtom } from "jotai";
|
||||
import { gameIdAtom, loginTokenAtom } from "../../atoms";
|
||||
import { gameIdAtom } from "../../atoms";
|
||||
import { Button } from "../../components/Button";
|
||||
import LeaderboardButton from "../../components/LeaderboardButton";
|
||||
import { ShareButton } from "../../components/ShareButton";
|
||||
|
|
@ -15,7 +15,6 @@ interface EndlessProps {
|
|||
|
||||
const Endless: React.FC<EndlessProps> = (props) => {
|
||||
const [gameId, setGameId] = useAtom(gameIdAtom);
|
||||
const [loginToken] = useAtom(loginTokenAtom);
|
||||
const { data: game } = useWSQuery("game.getGameState", gameId!, !!gameId);
|
||||
const { data: settings } = useWSQuery("user.getSettings", null);
|
||||
const { data: currentUsername } = useWSQuery("user.getSelf", null);
|
||||
|
|
@ -93,7 +92,7 @@ const Endless: React.FC<EndlessProps> = (props) => {
|
|||
<div className="w-full grid md:grid-cols-[350px_1fr]">
|
||||
<div className="flex flex-col md:border-r-white/10 md:border-r-1 gap-8 pr-12">
|
||||
<h2 className="text-white/90 text-xl">Minesweeper Endless</h2>
|
||||
{loginToken ? (
|
||||
{currentUsername ? (
|
||||
<Button
|
||||
className="w-fit"
|
||||
variant="primary"
|
||||
|
|
@ -107,7 +106,7 @@ const Endless: React.FC<EndlessProps> = (props) => {
|
|||
Start Game
|
||||
</Button>
|
||||
) : (
|
||||
<RegisterButton />
|
||||
<RegisterButton label="Sign in to Play" />
|
||||
)}
|
||||
<h2 className="text-white/80 text-lg mt-8">How to play</h2>
|
||||
<p className="text-white/90">
|
||||
|
|
|
|||
|
|
@ -18,11 +18,13 @@ import { useEffect } from "react";
|
|||
import { initParticlesEngine, Particles as ParticlesComponent } from "@tsparticles/react";
|
||||
import { motion } from "motion/react";
|
||||
import BounceImg from "../../components/BounceImg";
|
||||
import RegisterButton from "../../components/Auth/RegisterButton";
|
||||
|
||||
const Store = () => {
|
||||
const openLootbox = useWSMutation("user.openLootbox");
|
||||
const [lootboxResult, setLootboxResult] = useAtom(lootboxResultAtom);
|
||||
const currentLootbox = lootboxes.find((l) => l.id === lootboxResult?.lootbox);
|
||||
const { data: username } = useWSQuery("user.getSelf", null);
|
||||
const { refetch } = useWSQuery("user.getOwnGems", null);
|
||||
|
||||
// this should be run only once per application lifetime
|
||||
|
|
@ -184,18 +186,24 @@ const Store = () => {
|
|||
</Dialog>
|
||||
</div>
|
||||
<BounceImg src={lootbox.image} className="w-[360px]" />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="default"
|
||||
className="mx-auto items-center"
|
||||
onClick={() => {
|
||||
openLootbox
|
||||
.mutateAsync({ id: lootbox.id })
|
||||
.then(() => refetch());
|
||||
}}
|
||||
>
|
||||
Buy for <b>{lootbox.priceText}</b> <GemsIcon />
|
||||
</Button>
|
||||
{username ? (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="default"
|
||||
className="mx-auto items-center"
|
||||
onClick={() => {
|
||||
openLootbox
|
||||
.mutateAsync({ id: lootbox.id })
|
||||
.then(() => refetch());
|
||||
}}
|
||||
>
|
||||
Buy for <b>{lootbox.priceText}</b> <GemsIcon />
|
||||
</Button>
|
||||
) : (
|
||||
<div className="flex justify-center">
|
||||
<RegisterButton label="Login to Buy" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue