Skip to content

Update project - #1

Merged
nikero41 merged 44 commits into
mainfrom
update-project
Feb 2, 2026
Merged

Update project#1
nikero41 merged 44 commits into
mainfrom
update-project

Conversation

@nikero41

@nikero41 nikero41 commented Feb 2, 2026

Copy link
Copy Markdown
Owner

No description provided.

@vercel

vercel Bot commented Feb 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
neighbors-game Ready Ready Preview, Comment Feb 2, 2026 4:22pm

@coderabbitai

coderabbitai Bot commented Feb 2, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@nikero41 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 49 minutes and 15 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Removes Redux and axios; adds GameStateContext with reducer, replaces axios with fetch + Zod validation, introduces React Query, upgrades tooling (React v19, ESLint/Prettier, Husky, commitlint, CI), refactors components to use context, and migrates Sass @import@use.

Changes

Cohort / File(s) Summary
Tooling & CI
eslint.config.js, prettier.config.js, .github/workflows/ci.yml, .node-version, commitlint.config.ts, lint-staged.config.ts, .husky/pre-commit, .husky/commit-msg, .eslintrc.json (deleted), .prettierrc (deleted), lint-staged.config.ts
Adds programmatic ESLint/Prettier configs, CI lint workflow, Node version pin, commitlint + lint-staged + Husky hooks; removes legacy JSON configs.
Package & build
package.json, tsconfig.json, vite.config.ts
Upgrades deps (React → v19, add @tanstack/react-query, zod), adds lint/format scripts, switches tsconfig to extend shared base and path aliases, reorders Vite plugins.
Entry & runtime setup
src/main.tsx, index.html, public/site.webmanifest, src/types/vite.d.ts, src/types/reset.d.ts
Switches to createRoot, wraps App with QueryClientProvider, removes Redux Provider/store, updates index.html head, adds webmanifest and Vite/ts-reset type refs.
State layer (new)
src/components/GameStateContext.tsx, src/helpers/gameState.ts
Adds GameStateProvider, useGameState hook, and gameState reducer/enum/actions implementing game flow, scoring, rounds and history.
Data & validation
src/helpers/country.ts, src/helpers/axios.ts (deleted), src/types/* (removed: src/types/country-api.types.ts, src/types/country.types.ts, src/types/round-info.types.ts)
Replaces axios helper with fetch-based fetchCountries that validates via Zod CountrySchema; adds Country type, pickMainCountry, generateCountriesGrid; removes legacy API types and axios error mapper.
React Query & fetching
src/App.tsx
App now uses React Query + GameStateContext; replaces Redux effects/selectors with data-driven rendering and provider wrapping.
Feature refactor / components
src/components/CountryCardsGrid/index.tsx, src/components/Sidebar/index.tsx, src/components/Title/index.tsx, src/components/ProgressBar/index.tsx, removed: src/components/Neighbors/*, removed old Header/* Title/ProgressBar`
Adds CountryCardsGrid; Sidebar, Title, ProgressBar rewritten to consume new context; removes many Redux-dependent Neighbors components.
UI primitives & reorg
src/components/UI/*src/components/ui/* (Card, Button, Modal and .module.scss), new src/components/ui/* files
Introduces named-export UI primitives (Card, Button, Modal) with SCSS modules, semantic button element, portal/backdrop modal behavior, and new ui path.
Sass migration & styling
src/global.scss, src/components/**/*.module.scss, src/components/ui/**/*.module.scss
Converts @import@use "/src/styles/variables" as *;, updates color API (color.scale), normalizes formatting across SCSS modules.
Removed Redux wiring
src/store/..., src/helpers/store.ts, src/types/store.types.ts
Removes Redux slices, thunk actions, reducers, store wiring and typed helper exports.
Utilities & hooks removed/changed
src/helpers/util.ts, src/hooks/use-error.tsx (deleted)
Minor typing/ESLint adjustments to shuffleArray; removes useError hook and axios error mapper.
Docs
README.md
Small README title and Table of Contents formatting updates.
Other new UI modules
src/components/ui/Button/*, src/components/ui/Card/*, src/components/ui/Modal/*
Adds new ui button/card/modal components and SCSS modules under src/components/ui.

Sequence Diagram(s)

sequenceDiagram
  participant User as User
  participant App as App (React)
  participant Query as QueryClient
  participant Fetch as fetch
  participant GameCtx as GameStateProvider
  participant Reducer as gameStateReducer

  User->>App: open app / interact
  App->>Query: useQuery(fetchCountries)
  Query->>Fetch: GET /countries
  Fetch-->>Query: countries JSON
  Query-->>App: provide countries
  App->>GameCtx: init (pickMainCountry / resetRound)
  User->>App: select CountryCard
  App->>GameCtx: gameAction.submitCountry(country)
  GameCtx->>Reducer: dispatch submitCountry(isCorrect)
  Reducer-->>GameCtx: update state (score/right/wrong/stage)
  GameCtx-->>App: UI re-renders with updated state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

"🐰 I hopped through code and cleared the mess,
Context now runs where reducers rest,
Fetch and Zod keep data neat,
ESLint, Prettier — tidy feet,
New spring, new hop, a cleaner nest!"

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Update project' is vague and generic, using non-descriptive language that fails to convey the specific nature of the substantial changes made. Revise the title to be more specific about the key changes, such as 'Migrate from Redux to React Query and context API' or 'Refactor state management and upgrade linting setup'.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a detailed pull request description explaining the objectives, major changes, and any migration notes or breaking changes introduced.
✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch update-project

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
README.md (2)

5-16: ⚠️ Potential issue | 🟡 Minor

Fix heading level and anchor fragments to match the new title.

### Table of Contents skips a heading level, and the #read-me-template anchors no longer exist after renaming the title.

✏️ Suggested fix
-### Table of Contents
+## Table of Contents

-- [Read Me Template](`#read-me-template`)
-  - [Table of Contents](`#table-of-contents`)
+-- [Neighbors Game](`#neighbors-game`)
+  - [Table of Contents](`#table-of-contents`)
...
-[Back To The Top](`#read-me-template`)
+[Back To The Top](`#neighbors-game`)

Also applies to: 33-33, 57-57, 63-63, 91-91, 100-100


24-31: ⚠️ Potential issue | 🟡 Minor

Update the Technologies list to match the fetch migration.

The README still lists Axios even though the PR replaces it with native fetch.

✏️ Suggested fix
-- Axios
+- Fetch API
🤖 Fix all issues with AI agents
In `@eslint.config.js`:
- Around line 31-33: The comment says console is allowed for debug and error
reporting but the ESLint rule "no-console" currently forbids all console usage;
update the "no-console" rule in eslint.config.js to permit the intended methods
(e.g., allow "debug" and "error" — or "warn"/"info" if desired) by changing the
rule value from a bare "error" to the object form that lists allowed console
methods, and keep the comment aligned with that change so the comment and the
"no-console" setting are consistent.
- Around line 197-198: The `react/jsx-filename-extension` rule in
eslint.config.js currently lists extensions as ["​.jsx", "tsx"] so `.tsx` lacks
its leading dot and won’t match; update the rule’s extensions array in the
`react/jsx-filename-extension` configuration to include the dot (change "tsx" to
".tsx") so .tsx files are correctly recognized by the linter.

In `@package.json`:
- Around line 6-14: The "lint" npm script uses ';' which is not cross-platform;
update the "lint" script in package.json (the "scripts" section, specifically
the "lint" entry) to use '&&' instead of ';' so both "lint:eslint" and "lint:ts"
run on Windows and POSIX shells, preserving the same sequencing and exit
behavior.

In `@public/site.webmanifest`:
- Line 1: The manifest's "name" and "short_name" fields are empty which causes
blank labels in install prompts; update the JSON in the site.webmanifest by
filling "name" with the full application name and "short_name" with a shorter
display name (e.g., app title and abbreviated title) so install dialogs and app
lists show proper labels—locate and edit the "name" and "short_name" keys in the
manifest to contain the appropriate strings.

In `@src/App.tsx`:
- Line 22: The linter flags the custom SCSS class "game-panel" in the JSX
className as an unknown Tailwind class; fix by either adding your SCSS to
Tailwind's content or safelisting the class: update or create tailwind.config.js
to include src/global.scss in the content globs or add "game-panel" to the
safelist, or alternatively disable the better-tailwindcss/no-unknown-classes
rule for non-Tailwind classes (globally in your ESLint config or inline for the
JSX line). Locate the class usage via the className "game-panel" in App.tsx and
the definition in src/global.scss, and change the tailwind.config.js or ESLint
rule accordingly.

In `@src/components/CountryCardsGrid/index.tsx`:
- Around line 81-87: Add an early return in handleCardClick to ignore clicks
when the game is finished: before the existing cardState guard, check the
current game stage via the game's API (e.g., inspect gameAction.stage or call a
provided getter like gameAction.getStage()) and return if it indicates the game
is over/finished; then proceed with the existing cardState check and the call to
gameAction.submitCountry, and only call setCardState(...) when the click is
allowed. Ensure you reference handleCardClick, gameAction.submitCountry,
setCardState and CardStateOptions when applying the guard.
- Around line 3-5: Update the import paths to match the actual directory casing
so TypeScript can resolve modules on case-sensitive filesystems: change the
imports that reference "@/components/ui/Card" and "@/components/ui/Modal" (and
any other "@/components/ui/*" usages) to the correct "@/components/UI/Card" and
"@/components/UI/Modal" while keeping the existing named symbols (Card, Modal,
useGameState) unchanged so the identifiers used in the CountryCardsGrid
component resolve correctly.
- Around line 90-94: The template expression for the Card className uses CSS
module lookups that can be undefined (cardStyles[cardState] and
cardStyles["not-found"]) which violates restrict-template-expressions; update
the interpolation to coerce these lookups to strings using nullish coalescing
(e.g., replace cardStyles[cardState] with cardStyles[cardState] ?? "" and
cardStyles["not-found"] with cardStyles["not-found"] ?? "") in the Card
component's className construction so the template never receives undefined.

In `@src/components/ErrorBoundary.tsx`:
- Line 3: The import in ErrorBoundary.tsx uses the wrong casing for the UI
folder; update the import statement that references Modal so it points to the
actual module with correct case (replace "@/components/ui/Modal" with
"@/components/UI/Modal") to match the existing directory and ensure the Modal
component (import Modal) resolves on case-sensitive filesystems.

In `@src/components/GameStateContext.tsx`:
- Around line 83-86: The JSX is using the context object GameStateContext as a
component which will crash; change the wrapper to use GameStateContext.Provider
and pass the value={{ gameState, gameAction }} so children are properly provided
the context (i.e., replace the GameStateContext element with
GameStateContext.Provider around {children}); ensure you keep the same value
props (gameState, gameAction) and children variable.

In `@src/components/ProgressBar/index.tsx`:
- Around line 9-17: The computed progress width can become NaN/Infinity when
totalCorrectAnswers is 0; update the calculation in ProgressBar to guard against
division by zero by deriving totalCorrectAnswers from
mainCountry?.borders.length ?? 0 and then computing progress as
totalCorrectAnswers > 0 ? (100 * rightAnswers) / totalCorrectAnswers : 0, and
ensure the inline style for width uses this safe progress value when rendering
the element with id "current-progress" (referencing totalCorrectAnswers,
progress, mainCountry and rightAnswers).

In `@src/components/Sidebar/index.tsx`:
- Around line 1-3: The import for the Button component in Sidebar (import {
Button } from "@/components/ui/Button") has the wrong casing and will fail on
case-sensitive filesystems; update the import path to match the actual directory
name (use "@/components/UI/Button") wherever Button is imported in this file
(Sidebar/index.tsx) so the module resolves correctly.

In `@src/components/UI/Button/index.tsx`:
- Around line 3-13: Default the className prop to an empty string in the Button
component signature to remove its nullish type (change the destructuring in
export const Button to use className = ""), so the template literal
`${styles["btn"]} ${active ? styles["btn--active"] : styles["btn--inactive"]}
${className}` no longer contains a possibly undefined value; update the prop
typing left as { active?: boolean } &
React.ButtonHTMLAttributes<HTMLButtonElement> and remove the redundant nullish
coalescing in the template.

In `@src/components/UI/Card/index.tsx`:
- Around line 3-12: The template literal in the Card component uses className
which is typed string | undefined, causing the eslint rule to fail; fix it by
defaulting className to an empty string in the component's parameter
destructuring (change the signature of Card to destructure className = ""
alongside children and restProps) so the JSX <div className={`${styles["card"]}
${className}`} ...> always receives a string; keep the prop type as className?:
string and no other code changes are needed.

In `@src/components/UI/Modal/index.tsx`:
- Line 3: The import in Modal's index.tsx uses the wrong casing for the UI
folder; update the import statement that imports Card (the symbol "Card" in
Modal/index.tsx) to use the correct folder name "UI" (uppercase) instead of "ui"
so the path matches the actual directory name and works on case-sensitive
filesystems.
- Around line 8-9: Module-level document.querySelector call for
overlaysDivElement causes crashes in SSR/tests; change it to a guarded, runtime
lookup and only throw when actually needed for teleporting. Replace the
top-level query with a safe check (typeof document !== "undefined") and perform
document.querySelector inside the component lifecycle or right before creating
the teleport/portal (referencing overlaysDivElement and the Modal/teleport code
paths), and only throw an error if the element lookup fails at that runtime
point when you intend to mount the portal; this keeps SSR/tests from failing
while preserving the existing runtime error when the overlays element is
missing.
🧹 Nitpick comments (3)
src/components/UI/Button/Button.module.scss (1)

10-15: Resolve or document the TODO on box-sizing.

Line 14 leaves a TODO comment; please either decide and remove it or link to an issue so it doesn’t linger as debt.

src/components/ErrorBoundary.tsx (1)

9-10: Type the Component state parameter for stronger setState typing.

Component is only parameterized with props, so state defaults to {}. Adding State improves type safety for state/setState.

♻️ Optional typing improvement
-export class ErrorBoundary extends Component<{ children: React.ReactNode }> {
+export class ErrorBoundary extends Component<{ children: React.ReactNode }, State> {
src/helpers/country.ts (1)

20-36: Add a fallback to avoid unbounded recursion when history is exhausted.

If history contains all eligible countries, pickMainCountry can recurse indefinitely. Consider selecting from the remaining pool or falling back to any eligible country.

♻️ Proposed refactor
-	const mainCountry = shuffleArray<Country>(countriesWithBorders)[0];
-	if (!mainCountry) throw new Error("Failed to pick random country");
-
-	const isInHistory = history.includes(mainCountry.name.common);
-	if (isInHistory) {
-		return pickMainCountry(countries, history);
-	}
-
-	return mainCountry as IMainCountry;
+	const remaining = countriesWithBorders.filter(
+		country => !history.includes(country.name.common),
+	);
+	const pool = remaining.length > 0 ? remaining : countriesWithBorders;
+	const mainCountry = shuffleArray<Country>([...pool])[0];
+	if (!mainCountry) throw new Error("Failed to pick random country");
+	return mainCountry as IMainCountry;

Comment thread eslint.config.js Outdated
Comment thread eslint.config.js Outdated
Comment thread package.json
Comment thread public/site.webmanifest Outdated
Comment thread src/App.tsx
Comment thread src/components/Sidebar/index.tsx
Comment thread src/components/UI/Button/index.tsx Outdated
Comment thread src/components/ui/Card/index.tsx
Comment thread src/components/ui/Modal/index.tsx
Comment thread src/components/ui/Modal/index.tsx

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In @.husky/pre-commit:
- Around line 1-3: The pre-commit hook currently overwrites NODE_OPTIONS and
unguardedly relies on a Node flag that requires Node >=22.6.0; update the
.husky/pre-commit to append the --experimental-strip-types flag to any existing
NODE_OPTIONS using shell parameter expansion (preserve existing values) and add
a minimum Node version in package.json "engines" (e.g., node >= 22.6.0) or
perform a runtime node version check in the hook before adding the flag to avoid
incompatibility.

In `@src/components/GameStateContext.tsx`:
- Around line 54-58: resetRound currently appends mainCountry to the persistent
history, which lets pickMainCountry skip over more candidates across game resets
and can eventually exhaust options and recurse; update resetRound (the
resetRound handler that uses pickMainCountry, history, setHistory, and
dispatch({ type: "resetRound", mainCountry })) to clear or reinitialize history
when starting a new game (e.g., setHistory([]) before adding the first pick) or
add a defensive fallback in pickMainCountry to return a candidate when no unused
countries remain so resetRound cannot trigger infinite recursion.

In `@src/components/ui/Button/index.tsx`:
- Around line 3-15: The Button component currently only omits onClick when
active is false but remains focusable and lacks disabled semantics; update the
Button (props active, onClick, restProps, className, styles["btn"] /
styles["btn--active"/"btn--inactive"]) so that when active is false you pass
disabled={true} and when active is true ensure disabled is not set (or set to
undefined), and also ensure a default type="button" is applied (but allow
overriding via restProps) while preserving the existing className logic and only
attaching onClick when active is true.

In `@src/helpers/country.ts`:
- Around line 33-53: pickMainCountry currently recurses indefinitely if history
contains all countriesWithBorders; instead compute an available pool by
filtering countriesWithBorders against history (e.g., availableCountries =
countriesWithBorders.filter(c => !history.includes(c.name.common))) and choose
the random mainCountry from that pool; if availableCountries is empty fall back
to a safe behavior such as clearing/resetting history or selecting from
countriesWithBorders and log/throw a clear error to avoid unbounded recursion;
update references to countriesWithBorders, mainCountry and history accordingly.
🧹 Nitpick comments (4)
src/components/ui/Button/Button.module.scss (2)

14-14: Resolve the TODO on box-sizing.
Leaving “todo” here makes the intent unclear; either document the rationale or remove the comment once decided.


3-9: justify-content has no effect on inline-block.
Consider switching to inline-flex (and optionally align-items: center) or removing justify-content.

♻️ Proposed tweak
 .btn {
 	font-size: 0.75em;
 	text-align: center;
-	justify-content: center;
+	display: inline-flex;
+	justify-content: center;
+	align-items: center;
-	display: inline-block;
src/components/ui/Modal/Modal.module.scss (1)

5-12: Consider fixed positioning for modal/backdrop.
Using position: fixed keeps the overlay stable during scroll and avoids reliance on a positioned ancestor.

♻️ Suggested tweak
.modal {
-	position: absolute;
+	position: fixed;
 	top: 50%;
 	left: 50%;
 	transform: translate(-50%, -50%);
 	z-index: 3;
 	width: 80%;
 	max-width: 400px;
@@
 	&__backdrop {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
+		position: fixed;
+		inset: 0;
 		z-index: 2;
 		background-color: rgba(17, 17, 17, 0.712);
 	}
}

Also applies to: 50-56

lint-staged.config.ts (1)

1-3: Split ESLint and Prettier globs unless ESLint is configured for JSON/MD.

Running ESLint on *.json/*.md can fail without the right parser/plugins. If ESLint isn’t configured for those, split the globs to avoid blocked commits.

♻️ Proposed config split
 export default {
-	"*.{ts,tsx,js,jsx,json,md}": ["eslint", "prettier --write"],
+	"*.{ts,tsx,js,jsx}": ["eslint", "prettier --write"],
+	"*.{json,md}": ["prettier --write"],
 };

Comment thread .husky/pre-commit
Comment thread src/components/GameStateContext.tsx
Comment thread src/components/ui/Button/index.tsx
Comment thread src/helpers/country.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/ErrorBoundary.tsx (1)

24-29: ⚠️ Potential issue | 🟡 Minor

Fix typo in error modal header.

"occured" should be "occurred" (missing an 'r'). This is user-facing text that will be displayed when an error is caught.

✏️ Suggested fix
 				<Modal
 					teleport
-					header="An error occured"
+					header="An error occurred"
 					body={this.state.error.message}
 				/>
🤖 Fix all issues with AI agents
In `@src/components/GameStateContext.tsx`:
- Around line 60-65: The submitCountry handler can crash because
gameState.mainCountry.borders may be undefined; change the correctness check in
submitCountry to safely handle missing borders (e.g., use optional chaining or
validate Array.isArray on gameState.mainCountry.borders before calling includes)
so the isCorrect computation becomes something like checking that
gameState.mainCountry exists and that its borders is an array that includes
country.cca3, then dispatch and return using that safe boolean result (keep
dispatch({ type: "submitCountry", isCorrect: !!isCorrect }) and return {
correct: !!isCorrect }).

In `@src/global.scss`:
- Around line 15-18: In global.scss update the font-family declaration to
correct the fallback typo: replace "san-serif" with the proper generic family
"sans-serif" in the font-family rule so the declaration reads Lato, Open Sans,
sans-serif (keep the existing order and quoting as-is) — locate the font-family
line in the global stylesheet to make the change.
🧹 Nitpick comments (2)
index.html (1)

17-17: Redundant defer attribute on module script.

ES modules (type="module") are deferred by default, so the explicit defer attribute is unnecessary.

♻️ Suggested fix
-		<script type="module" src="/src/main.tsx" defer></script>
+		<script type="module" src="/src/main.tsx"></script>
src/helpers/country.ts (1)

53-79: Rename isBoardCountryisBorderCountry for clarity.
Minor typo that slightly hurts readability.

♻️ Proposed rename
-			const isBoardCountry = mainCountry.borders.includes(country.cca3);
-			if (isBoardCountry) {
+			const isBorderCountry = mainCountry.borders.includes(country.cca3);
+			if (isBorderCountry) {

Comment thread src/components/GameStateContext.tsx
Comment thread src/global.scss

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/helpers/country.ts`:
- Around line 20-25: CountrySchema's borders property can be omitted by the API;
update the CountrySchema definition so the borders key uses
z.array(z.string()).default([]) (i.e., change the borders schema in
CountrySchema to provide an empty-array default) so validation succeeds for
island countries and downstream code that filters on borders continues to work.
🧹 Nitpick comments (1)
src/helpers/gameState.ts (1)

17-72: Add a default / assertNever path so the reducer never returns undefined.

If an unexpected action slips through at runtime, the reducer currently falls off the switch. A default return (or an assertNever) prevents accidental undefined state.

🔧 Suggested fix
 export const gameStateReducer = (
 	state: GameState,
 	action: GameStatePayload,
 ) => {
 	switch (action.type) {
 		case "nextRound": {
 			if (state.stage !== GameStage.Won) return state;
 			return {
 				...state,
 				round: state.round + 1,
 				rightAnswers: 0,
 				wrongAnswers: 0,
 				stage: GameStage.OnGoing,
 				mainCountry: action.mainCountry,
 			};
 		}
 		case "resetRound": {
 			return {
 				...state,
 				round: 1,
 				score: 0,
 				rightAnswers: 0,
 				wrongAnswers: 0,
 				stage: GameStage.OnGoing,
 				mainCountry: action.mainCountry,
 			};
 		}
 		case "submitCountry": {
 			const scoreChange = action.isCorrect ? 5 : -3;

 			const newState = {
 				...state,
 				score: state.score + scoreChange,
 				...(action.isCorrect
 					? { rightAnswers: state.rightAnswers + 1 }
 					: { wrongAnswers: state.wrongAnswers + 1 }),
 			};

 			const hasEnded =
 				newState.mainCountry?.borders.length === newState.rightAnswers ||
 				newState.mainCountry?.borders.length === newState.wrongAnswers;

 			if (hasEnded) {
 				return {
 					...newState,
 					stage:
 						newState.rightAnswers > newState.wrongAnswers
 							? GameStage.Won
 							: GameStage.Lost,
 				};
 			}

 			return newState;
 		}
+		default:
+			return state;
 	}
 };

Comment thread src/helpers/country.ts
@nikero41
nikero41 merged commit 524cfc1 into main Feb 2, 2026
3 of 4 checks passed
@nikero41
nikero41 deleted the update-project branch February 2, 2026 16:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant