Update project - #1
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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. 📝 WalkthroughWalkthroughRemoves 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 Changes
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 | 🟡 MinorFix heading level and anchor fragments to match the new title.
### Table of Contentsskips a heading level, and the#read-me-templateanchors 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 | 🟡 MinorUpdate 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 onbox-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 strongersetStatetyping.
Componentis only parameterized with props, so state defaults to{}. AddingStateimproves type safety forstate/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
historycontains all eligible countries,pickMainCountrycan 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;
There was a problem hiding this comment.
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 onbox-sizing.
Leaving “todo” here makes the intent unclear; either document the rationale or remove the comment once decided.
3-9:justify-contenthas no effect oninline-block.
Consider switching toinline-flex(and optionallyalign-items: center) or removingjustify-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.
Usingposition: fixedkeeps 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/*.mdcan 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"], };
this avoids infinite recursions and unnessesary array loops
There was a problem hiding this comment.
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 | 🟡 MinorFix 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: Redundantdeferattribute on module script.ES modules (
type="module") are deferred by default, so the explicitdeferattribute 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: RenameisBoardCountry→isBorderCountryfor 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) {
There was a problem hiding this comment.
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 /assertNeverpath 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 accidentalundefinedstate.🔧 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; } };
No description provided.