fix(lint): resolve all eslint errors across monorepo
- Type response bodies in gameController.test.ts to fix no-unsafe-member-access - Replace async methods with Promise.resolve() in InMemoryGameSessionStore and InMemoryLobbyGameStore to satisfy require-await rule - Add argsIgnorePattern and varsIgnorePattern to eslint config so underscore-prefixed params are globally ignored - Fix no-misused-promises in ws/index.ts, lobbyHandlers, gameHandlers, __root.tsx, login.tsx and play.tsx by using void + .catch() - Fix no-floating-promises on navigate calls in login.tsx - Move API_URL outside Play component to fix useCallback dependency warning - Type fetch response bodies in play.tsx to fix no-unsafe-assignment - Add only-throw-error: off for route files (TanStack Router throw redirect) - Remove unused WebSocket import from express.d.ts - Fix unsafe return in connections.ts by typing empty Map constructor - Exclude scripts/ folder from eslint - Add targeted override for better-auth auth-client.ts (upstream typing issue)
This commit is contained in:
parent
a6d8ddec3b
commit
ce19740cc8
12 changed files with 160 additions and 91 deletions
|
|
@ -1,5 +1,11 @@
|
||||||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||||
import request from "supertest";
|
import request from "supertest";
|
||||||
|
import type { GameSession, AnswerResult } from "@lila/shared";
|
||||||
|
|
||||||
|
type SuccessResponse<T> = { success: true; data: T };
|
||||||
|
type ErrorResponse = { success: false; error: string };
|
||||||
|
type GameStartResponse = SuccessResponse<GameSession>;
|
||||||
|
type GameAnswerResponse = SuccessResponse<AnswerResult>;
|
||||||
|
|
||||||
vi.mock("@lila/db", () => ({ getGameTerms: vi.fn(), getDistractors: vi.fn() }));
|
vi.mock("@lila/db", () => ({ getGameTerms: vi.fn(), getDistractors: vi.fn() }));
|
||||||
|
|
||||||
|
|
@ -33,49 +39,48 @@ beforeEach(() => {
|
||||||
describe("POST /api/v1/game/start", () => {
|
describe("POST /api/v1/game/start", () => {
|
||||||
it("returns 200 with a valid game session", async () => {
|
it("returns 200 with a valid game session", async () => {
|
||||||
const res = await request(app).post("/api/v1/game/start").send(validBody);
|
const res = await request(app).post("/api/v1/game/start").send(validBody);
|
||||||
|
const body = res.body as GameStartResponse;
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
expect(res.body.success).toBe(true);
|
expect(body.success).toBe(true);
|
||||||
expect(res.body.data.sessionId).toBeDefined();
|
expect(body.data.sessionId).toBeDefined();
|
||||||
expect(res.body.data.questions).toHaveLength(3);
|
expect(body.data.questions).toHaveLength(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 400 when the body is empty", async () => {
|
it("returns 400 when the body is empty", async () => {
|
||||||
const res = await request(app).post("/api/v1/game/start").send({});
|
const res = await request(app).post("/api/v1/game/start").send({});
|
||||||
|
const body = res.body as ErrorResponse;
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.success).toBe(false);
|
expect(body.success).toBe(false);
|
||||||
expect(res.body.error).toBeDefined();
|
expect(body.error).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 400 when required fields are missing", async () => {
|
it("returns 400 when required fields are missing", async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post("/api/v1/game/start")
|
.post("/api/v1/game/start")
|
||||||
.send({ source_language: "en" });
|
.send({ source_language: "en" });
|
||||||
|
const body = res.body as ErrorResponse;
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.success).toBe(false);
|
expect(body.success).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 400 when a field has an invalid value", async () => {
|
it("returns 400 when a field has an invalid value", async () => {
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post("/api/v1/game/start")
|
.post("/api/v1/game/start")
|
||||||
.send({ ...validBody, difficulty: "impossible" });
|
.send({ ...validBody, difficulty: "impossible" });
|
||||||
|
const body = res.body as ErrorResponse;
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.success).toBe(false);
|
expect(body.success).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("POST /api/v1/game/answer", () => {
|
describe("POST /api/v1/game/answer", () => {
|
||||||
it("returns 200 with an answer result for a valid submission", async () => {
|
it("returns 200 with an answer result for a valid submission", async () => {
|
||||||
// Start a game first
|
|
||||||
const startRes = await request(app)
|
const startRes = await request(app)
|
||||||
.post("/api/v1/game/start")
|
.post("/api/v1/game/start")
|
||||||
.send(validBody);
|
.send(validBody);
|
||||||
|
const startBody = startRes.body as GameStartResponse;
|
||||||
const { sessionId, questions } = startRes.body.data;
|
const { sessionId, questions } = startBody.data;
|
||||||
const question = questions[0];
|
const question = questions[0]!;
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post("/api/v1/game/answer")
|
.post("/api/v1/game/answer")
|
||||||
|
|
@ -84,20 +89,20 @@ describe("POST /api/v1/game/answer", () => {
|
||||||
questionId: question.questionId,
|
questionId: question.questionId,
|
||||||
selectedOptionId: 0,
|
selectedOptionId: 0,
|
||||||
});
|
});
|
||||||
|
const body = res.body as GameAnswerResponse;
|
||||||
expect(res.status).toBe(200);
|
expect(res.status).toBe(200);
|
||||||
expect(res.body.success).toBe(true);
|
expect(body.success).toBe(true);
|
||||||
expect(res.body.data.questionId).toBe(question.questionId);
|
expect(body.data.questionId).toBe(question.questionId);
|
||||||
expect(typeof res.body.data.isCorrect).toBe("boolean");
|
expect(typeof body.data.isCorrect).toBe("boolean");
|
||||||
expect(typeof res.body.data.correctOptionId).toBe("number");
|
expect(typeof body.data.correctOptionId).toBe("number");
|
||||||
expect(res.body.data.selectedOptionId).toBe(0);
|
expect(body.data.selectedOptionId).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 400 when the body is empty", async () => {
|
it("returns 400 when the body is empty", async () => {
|
||||||
const res = await request(app).post("/api/v1/game/answer").send({});
|
const res = await request(app).post("/api/v1/game/answer").send({});
|
||||||
|
const body = res.body as ErrorResponse;
|
||||||
expect(res.status).toBe(400);
|
expect(res.status).toBe(400);
|
||||||
expect(res.body.success).toBe(false);
|
expect(body.success).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 404 when the session does not exist", async () => {
|
it("returns 404 when the session does not exist", async () => {
|
||||||
|
|
@ -108,18 +113,18 @@ describe("POST /api/v1/game/answer", () => {
|
||||||
questionId: "00000000-0000-0000-0000-000000000000",
|
questionId: "00000000-0000-0000-0000-000000000000",
|
||||||
selectedOptionId: 0,
|
selectedOptionId: 0,
|
||||||
});
|
});
|
||||||
|
const body = res.body as ErrorResponse;
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
expect(res.body.success).toBe(false);
|
expect(body.success).toBe(false);
|
||||||
expect(res.body.error).toContain("Game session not found");
|
expect(body.error).toContain("Game session not found");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("returns 404 when the question does not exist in the session", async () => {
|
it("returns 404 when the question does not exist in the session", async () => {
|
||||||
const startRes = await request(app)
|
const startRes = await request(app)
|
||||||
.post("/api/v1/game/start")
|
.post("/api/v1/game/start")
|
||||||
.send(validBody);
|
.send(validBody);
|
||||||
|
const startBody = startRes.body as GameStartResponse;
|
||||||
const { sessionId } = startRes.body.data;
|
const { sessionId } = startBody.data;
|
||||||
|
|
||||||
const res = await request(app)
|
const res = await request(app)
|
||||||
.post("/api/v1/game/answer")
|
.post("/api/v1/game/answer")
|
||||||
|
|
@ -128,9 +133,9 @@ describe("POST /api/v1/game/answer", () => {
|
||||||
questionId: "00000000-0000-0000-0000-000000000000",
|
questionId: "00000000-0000-0000-0000-000000000000",
|
||||||
selectedOptionId: 0,
|
selectedOptionId: 0,
|
||||||
});
|
});
|
||||||
|
const body = res.body as ErrorResponse;
|
||||||
expect(res.status).toBe(404);
|
expect(res.status).toBe(404);
|
||||||
expect(res.body.success).toBe(false);
|
expect(body.success).toBe(false);
|
||||||
expect(res.body.error).toContain("Question not found");
|
expect(body.error).toContain("Question not found");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,17 @@ import type { GameSessionStore, GameSessionData } from "./GameSessionStore.js";
|
||||||
export class InMemoryGameSessionStore implements GameSessionStore {
|
export class InMemoryGameSessionStore implements GameSessionStore {
|
||||||
private sessions = new Map<string, GameSessionData>();
|
private sessions = new Map<string, GameSessionData>();
|
||||||
|
|
||||||
async create(sessionId: string, data: GameSessionData): Promise<void> {
|
create(sessionId: string, data: GameSessionData): Promise<void> {
|
||||||
this.sessions.set(sessionId, data);
|
this.sessions.set(sessionId, data);
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(sessionId: string): Promise<GameSessionData | null> {
|
get(sessionId: string): Promise<GameSessionData | null> {
|
||||||
return this.sessions.get(sessionId) ?? null;
|
return Promise.resolve(this.sessions.get(sessionId) ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(sessionId: string): Promise<void> {
|
delete(sessionId: string): Promise<void> {
|
||||||
this.sessions.delete(sessionId);
|
this.sessions.delete(sessionId);
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,22 +3,25 @@ import type { LobbyGameStore, LobbyGameData } from "./LobbyGameStore.js";
|
||||||
export class InMemoryLobbyGameStore implements LobbyGameStore {
|
export class InMemoryLobbyGameStore implements LobbyGameStore {
|
||||||
private games = new Map<string, LobbyGameData>();
|
private games = new Map<string, LobbyGameData>();
|
||||||
|
|
||||||
async create(lobbyId: string, data: LobbyGameData): Promise<void> {
|
create(lobbyId: string, data: LobbyGameData): Promise<void> {
|
||||||
if (this.games.has(lobbyId)) {
|
if (this.games.has(lobbyId)) {
|
||||||
throw new Error(`Game already exists for lobby: ${lobbyId}`);
|
throw new Error(`Game already exists for lobby: ${lobbyId}`);
|
||||||
}
|
}
|
||||||
this.games.set(lobbyId, data);
|
this.games.set(lobbyId, data);
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async get(lobbyId: string): Promise<LobbyGameData | null> {
|
get(lobbyId: string): Promise<LobbyGameData | null> {
|
||||||
return this.games.get(lobbyId) ?? null;
|
return Promise.resolve(this.games.get(lobbyId) ?? null);
|
||||||
}
|
}
|
||||||
|
|
||||||
async set(lobbyId: string, data: LobbyGameData): Promise<void> {
|
set(lobbyId: string, data: LobbyGameData): Promise<void> {
|
||||||
this.games.set(lobbyId, data);
|
this.games.set(lobbyId, data);
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(lobbyId: string): Promise<void> {
|
delete(lobbyId: string): Promise<void> {
|
||||||
this.games.delete(lobbyId);
|
this.games.delete(lobbyId);
|
||||||
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
apps/api/src/types/express.d.ts
vendored
1
apps/api/src/types/express.d.ts
vendored
|
|
@ -1,5 +1,4 @@
|
||||||
import type { Session, User } from "better-auth";
|
import type { Session, User } from "better-auth";
|
||||||
import type { WebSocket } from "ws";
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
namespace Express {
|
namespace Express {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ export const removeConnection = (lobbyId: string, userId: string): void => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getConnections = (lobbyId: string): Map<string, WebSocket> => {
|
export const getConnections = (lobbyId: string): Map<string, WebSocket> => {
|
||||||
return connections.get(lobbyId) ?? new Map();
|
return connections.get(lobbyId) ?? new Map<string, WebSocket>();
|
||||||
};
|
};
|
||||||
|
|
||||||
export const broadcastToLobby = (
|
export const broadcastToLobby = (
|
||||||
|
|
|
||||||
|
|
@ -126,7 +126,8 @@ export const resolveRound = async (
|
||||||
await endGame(lobbyId, state);
|
await endGame(lobbyId, state);
|
||||||
} else {
|
} else {
|
||||||
// Wait 3s then broadcast next question
|
// Wait 3s then broadcast next question
|
||||||
setTimeout(async () => {
|
setTimeout(() => {
|
||||||
|
void (async () => {
|
||||||
const fresh = await lobbyGameStore.get(lobbyId);
|
const fresh = await lobbyGameStore.get(lobbyId);
|
||||||
if (!fresh) return;
|
if (!fresh) return;
|
||||||
const nextQuestion = fresh.questions[fresh.currentIndex];
|
const nextQuestion = fresh.questions[fresh.currentIndex];
|
||||||
|
|
@ -143,10 +144,11 @@ export const resolveRound = async (
|
||||||
totalQuestions,
|
totalQuestions,
|
||||||
});
|
});
|
||||||
// Restart timer for next round
|
// Restart timer for next round
|
||||||
const timer = setTimeout(async () => {
|
const timer = setTimeout(() => {
|
||||||
await resolveRound(lobbyId, fresh.currentIndex, totalQuestions);
|
void resolveRound(lobbyId, fresh.currentIndex, totalQuestions);
|
||||||
}, 15000);
|
}, 15000);
|
||||||
timers.set(lobbyId, timer);
|
timers.set(lobbyId, timer);
|
||||||
|
})();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -148,8 +148,10 @@ const startRoundTimer = (
|
||||||
questionIndex: number,
|
questionIndex: number,
|
||||||
totalQuestions: number,
|
totalQuestions: number,
|
||||||
): void => {
|
): void => {
|
||||||
const timer = setTimeout(async () => {
|
const timer = setTimeout(() => {
|
||||||
await resolveRound(lobbyId, questionIndex, totalQuestions);
|
void resolveRound(lobbyId, questionIndex, totalQuestions).catch((err) => {
|
||||||
|
console.error("Error resolving round after timeout:", err);
|
||||||
|
});
|
||||||
}, 15000);
|
}, 15000);
|
||||||
timers.set(lobbyId, timer);
|
timers.set(lobbyId, timer);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -15,18 +15,31 @@ export const setupWebSocket = (server: Server): WebSocketServer => {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleUpgrade(request, socket, head, wss);
|
void handleUpgrade(request, socket, head, wss).catch((err) => {
|
||||||
|
console.error("WebSocket upgrade error:", err);
|
||||||
|
socket.destroy();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
wss.on(
|
wss.on(
|
||||||
"connection",
|
"connection",
|
||||||
(ws: WebSocket, _request: IncomingMessage, auth: AuthenticatedUser) => {
|
(ws: WebSocket, _request: IncomingMessage, auth: AuthenticatedUser) => {
|
||||||
ws.on("message", (rawData) => {
|
ws.on("message", (rawData) => {
|
||||||
handleMessage(ws, rawData, auth);
|
void handleMessage(ws, rawData, auth).catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`WebSocket message error for user ${auth.user.id}:`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on("close", () => {
|
ws.on("close", () => {
|
||||||
handleDisconnect(ws, auth);
|
void handleDisconnect(ws, auth).catch((err) => {
|
||||||
|
console.error(
|
||||||
|
`WebSocket disconnect error for user ${auth.user.id}:`,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on("error", (err) => {
|
ws.on("error", (err) => {
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,13 @@ const RootLayout = () => {
|
||||||
{session ? (
|
{session ? (
|
||||||
<button
|
<button
|
||||||
className="text-sm text-gray-600 hover:text-gray-900"
|
className="text-sm text-gray-600 hover:text-gray-900"
|
||||||
onClick={async () => {
|
onClick={() => {
|
||||||
|
void (async () => {
|
||||||
await signOut();
|
await signOut();
|
||||||
navigate({ to: "/" });
|
void navigate({ to: "/" });
|
||||||
|
})().catch((err) => {
|
||||||
|
console.error("Sign out error:", err);
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sign out ({session.user.name})
|
Sign out ({session.user.name})
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ const LoginPage = () => {
|
||||||
if (isPending) return <div className="p-4">Loading...</div>;
|
if (isPending) return <div className="p-4">Loading...</div>;
|
||||||
|
|
||||||
if (session) {
|
if (session) {
|
||||||
navigate({ to: "/" });
|
void navigate({ to: "/" });
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,23 +17,25 @@ const LoginPage = () => {
|
||||||
<h1 className="text-2xl font-bold">sign in to lila</h1>
|
<h1 className="text-2xl font-bold">sign in to lila</h1>
|
||||||
<button
|
<button
|
||||||
className="w-64 rounded bg-gray-800 px-4 py-2 text-white hover:bg-gray-700"
|
className="w-64 rounded bg-gray-800 px-4 py-2 text-white hover:bg-gray-700"
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
signIn.social({
|
void signIn
|
||||||
provider: "github",
|
.social({ provider: "github", callbackURL: window.location.origin })
|
||||||
callbackURL: window.location.origin,
|
.catch((err) => {
|
||||||
})
|
console.error("GitHub sign in error:", err);
|
||||||
}
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Continue with GitHub
|
Continue with GitHub
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="w-64 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-500"
|
className="w-64 rounded bg-blue-600 px-4 py-2 text-white hover:bg-blue-500"
|
||||||
onClick={() =>
|
onClick={() => {
|
||||||
signIn.social({
|
void signIn
|
||||||
provider: "google",
|
.social({ provider: "google", callbackURL: window.location.origin })
|
||||||
callbackURL: window.location.origin,
|
.catch((err) => {
|
||||||
})
|
console.error("Google sign in error:", err);
|
||||||
}
|
});
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
Continue with Google
|
Continue with Google
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,12 @@ import { ScoreScreen } from "../components/game/ScoreScreen";
|
||||||
import { GameSetup } from "../components/game/GameSetup";
|
import { GameSetup } from "../components/game/GameSetup";
|
||||||
import { authClient } from "../lib/auth-client";
|
import { authClient } from "../lib/auth-client";
|
||||||
|
|
||||||
function Play() {
|
type GameStartResponse = { success: true; data: GameSession };
|
||||||
const API_URL = import.meta.env["VITE_API_URL"] || "";
|
type GameAnswerResponse = { success: true; data: AnswerResult };
|
||||||
|
|
||||||
|
const API_URL = (import.meta.env["VITE_API_URL"] as string) || "";
|
||||||
|
|
||||||
|
function Play() {
|
||||||
const [gameSession, setGameSession] = useState<GameSession | null>(null);
|
const [gameSession, setGameSession] = useState<GameSession | null>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
const [currentQuestionIndex, setCurrentQuestionIndex] = useState(0);
|
||||||
|
|
@ -17,13 +20,15 @@ function Play() {
|
||||||
|
|
||||||
const startGame = useCallback(async (settings: GameRequest) => {
|
const startGame = useCallback(async (settings: GameRequest) => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const response = await fetch(`${API_URL}/api/v1/game/start`, {
|
const response = await fetch(`${API_URL}/api/v1/game/start`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
body: JSON.stringify(settings),
|
body: JSON.stringify(settings),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
|
||||||
|
const data = (await response.json()) as GameStartResponse;
|
||||||
setGameSession(data.data);
|
setGameSession(data.data);
|
||||||
setCurrentQuestionIndex(0);
|
setCurrentQuestionIndex(0);
|
||||||
setResults([]);
|
setResults([]);
|
||||||
|
|
@ -55,7 +60,7 @@ function Play() {
|
||||||
selectedOptionId: optionId,
|
selectedOptionId: optionId,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const data = await response.json();
|
const data = (await response.json()) as GameAnswerResponse;
|
||||||
setCurrentResult(data.data);
|
setCurrentResult(data.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -70,7 +75,13 @@ function Play() {
|
||||||
if (!gameSession && !isLoading) {
|
if (!gameSession && !isLoading) {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-linear-to-b from-purple-100 to-pink-50 flex items-center justify-center p-6">
|
<div className="min-h-screen bg-linear-to-b from-purple-100 to-pink-50 flex items-center justify-center p-6">
|
||||||
<GameSetup onStart={startGame} />
|
<GameSetup
|
||||||
|
onStart={(settings) => {
|
||||||
|
void startGame(settings).catch((err) => {
|
||||||
|
console.error("Start game error:", err);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -99,11 +110,15 @@ function Play() {
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-linear-to-b from-purple-100 to-pink-50 flex items-center justify-center p-6">
|
<div className="min-h-screen bg-linear-to-b from-purple-100 to-pink-50 flex items-center justify-center p-6">
|
||||||
<QuestionCard
|
<QuestionCard
|
||||||
|
onAnswer={(optionId) => {
|
||||||
|
void handleAnswer(optionId).catch((err) => {
|
||||||
|
console.error("Answer error:", err);
|
||||||
|
});
|
||||||
|
}}
|
||||||
question={question}
|
question={question}
|
||||||
questionNumber={currentQuestionIndex + 1}
|
questionNumber={currentQuestionIndex + 1}
|
||||||
totalQuestions={gameSession.questions.length}
|
totalQuestions={gameSession.questions.length}
|
||||||
currentResult={currentResult}
|
currentResult={currentResult}
|
||||||
onAnswer={handleAnswer}
|
|
||||||
onNext={handleNext}
|
onNext={handleNext}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ export default defineConfig([
|
||||||
"eslint.config.mjs",
|
"eslint.config.mjs",
|
||||||
"**/*.config.ts",
|
"**/*.config.ts",
|
||||||
"routeTree.gen.ts",
|
"routeTree.gen.ts",
|
||||||
|
"scripts/**",
|
||||||
]),
|
]),
|
||||||
|
|
||||||
eslint.configs.recommended,
|
eslint.configs.recommended,
|
||||||
|
|
@ -38,6 +39,27 @@ export default defineConfig([
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
files: ["apps/web/src/routes/**/*.{ts,tsx}"],
|
files: ["apps/web/src/routes/**/*.{ts,tsx}"],
|
||||||
rules: { "react-refresh/only-export-components": "off" },
|
rules: {
|
||||||
|
"react-refresh/only-export-components": "off",
|
||||||
|
"@typescript-eslint/only-throw-error": "off",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/no-unused-vars": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
argsIgnorePattern: "^_",
|
||||||
|
varsIgnorePattern: "^_",
|
||||||
|
caughtErrorsIgnorePattern: "^_",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// better-auth's createAuthClient return type is insufficiently typed upstream.
|
||||||
|
// This is a known issue: https://github.com/better-auth/better-auth/issues
|
||||||
|
files: ["apps/web/src/lib/auth-client.ts"],
|
||||||
|
rules: { "@typescript-eslint/no-unsafe-assignment": "off" },
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue