Skip to content

Feature/frontend redesign#16

Merged
izachoc merged 26 commits into
devfrom
feature/frontend-redesign
May 13, 2026
Merged

Feature/frontend redesign#16
izachoc merged 26 commits into
devfrom
feature/frontend-redesign

Conversation

@izachoc
Copy link
Copy Markdown
Member

@izachoc izachoc commented May 5, 2026

Чучуть оновив дизайн фронтенду, якщо коротко то я забув що міняв давно, але загалом перекопав майже все. Ось що тут є:

  • Повністю перероблена Hero-секція та головна сторінка, я накинув плавні анімації через Framer Motion. Хедер тепер адаптивний, з гласморфізмом, перемикачем тем і нормальними дропдаунами. Всі інші сторінки (404, FAQ, контакти, підтримка) причесав під єдиний дизайн і нормальну темну тему.
  • Зібрав нову багатокрокову форму реєстрації на турнір, прикрутив жорстку валідацію через Zod і додав крутий 3D-квиток. Також оновив леяути для авторизації.
  • Відрефакторив сторінки турнірів, додав динамічні статуси і плавні таби. А рулетка на головній тепер розумна, крутить і видає тільки активні івенти.
  • Ще я пофіксив біль із довгим завантаженням. Викинув завантаження з беку, переніс json-и з перекладами. Тепер сайт стартує миттєво в потрібній мові і темі без "білого екрану смерті".
    Коротше, стало швидше, темніше і красивіше і загалом краса неписана (вроді, я надіюсь)
    Ну я попрацював над адаптивністю під різні пристрої і іншим, надіюсь гарно :)

Summary by CodeRabbit

  • New Features

    • Dark/light theme with automatic detection + Theme toggle and language switcher; full English + Ukrainian localization
    • Tournament registration flow (team/solo) with interactive 3D ticket preview and animated roulette winner
    • New UI components: branding panel, registration steps, date/time picker, notifications, profile menu, and scroll-to-top
  • Style

    • Design-system tokens, refreshed theme-aware styles, updated fonts and icons, consistent animations
  • Tests

    • Updated i18n-aware tests and component test adjustments

Review Change Stack

@izachoc izachoc requested review from AnnPoshtak and Fundi1330 May 5, 2026 06:53
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

Adds i18n (EN/UK) and dark theme boot, replaces many pages/components with translated, animated variants, introduces a full multi-step tournament registration flow with validation and preview, adds UI primitives/utilities, updates routing, mock data, app bootstrap (toasts, sockets), and updates tests.

Changes

Frontend overhaul and new registration flow

Layer / File(s) Summary
Index, theme boot, i18n resources
frontend/index.html, frontend/src/index.css, frontend/src/i18n/config.ts, frontend/src/locales/*
Font switch and inline theme boot script/style, CSS theme-token variables and Tailwind mapping, i18n singleton init and added EN/UK locale payloads across namespaces.
App bootstrap: toasts & sockets
frontend/src/App.tsx, frontend/src/firebase.ts
Replaces sonner with ToastContainer, synchronizes theme and toast theme, refactors Socket.IO lifecycle to onIdTokenChanged, dispatches/clears user profile, and adds rate-limited toasts and cleanup.
Utilities & UI primitives
frontend/src/utils/cn.ts, frontend/src/components/ui/*, frontend/src/hooks/*, frontend/src/components/icons/*
Adds cn helper, ThemeToggle, LanguageSwitcher, BrandingPanel, Ticket3D, Button refactor, ScrollToTop, useClickOutside hook, brand icons, and updates UI exports.
Registration feature
frontend/src/pages/RegistrationPage/*, frontend/src/pages/RegistrationPage/components/*, frontend/src/pages/RegistrationPage/types.ts, frontend/src/data/mockTournaments.ts, frontend/src/routers/Router.tsx
New multi-step registration flow with dynamic Zod schema, form steps (general/members/confirm/success), form UI primitives, Ticket3D live preview, mock data extensions (min/max/customFields), and router route.
Pages & components localized/refactored
frontend/src/components/*, frontend/src/pages/*, frontend/src/config/navigation.ts
Header, Footer, Hero, Home, TournamentSlider, About, Contact, FAQ, Rules, Support, Auth, ForgotPassword, Profile, Tournaments, TournamentCard, ProfileDropdown, NotificationsDropdown and layout updates: all localized, tokenized, animated, and wired to new utilities and i18n.
Tests and minor adjustments
frontend/src/pages/Profile/*, frontend/package.json, frontend/src/slices/user.ts
Tests updated for i18n and prop-driven components; package.json dependencies added (i18n stack, clsx, tailwind-merge); small firebase/log cleanup and slice action addition (clearUser).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A rabbit taps its keys at night,
Weaving themes of dark and light;
Two tongues twirl in tidy rows,
Routes unfurl where signup flows;
Tickets tilt in 3D cheer—hop, frontend springs to life. ✨🐇

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Copy link
Copy Markdown
Collaborator

@AnnPoshtak AnnPoshtak left a comment

Choose a reason for hiding this comment

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

Такс. По перше все виглядає мега гарна. Дуже класний єфект з рандомним турніром. Сторінки почали швидше вантажитися.

Але не на всі сторінки є стилі. І деякі дивно вигяладають, бо там немає тексту, а лише hero.title, hero.description.

Image

Немає перекладу та темної теми на сторінку отримання ролі організатора, профіля.

На деяких сторінкаї хедер не видно, якщо не пролистати вниз

Image Image

По коду у мене критичних зауважень немає, все круто.

Але також зараз тоді треба дуже швидко переписати нові сторінки під такий стиль, додати їм перклад, написати їм темну тему.
А також тепер треба переписати та додати нові тести через те що зявлися інтернацоналізація та темна тема.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Note

Unit test generation is a public access feature. Expect some limitations and changes as we gather feedback and continue to improve it.


Generating unit tests... This may take up to 20 minutes.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

✅ Created PR with unit tests: #17

@Fundi1330
Copy link
Copy Markdown
Member

Прийду додому і гляну все, як виглядає. Код наче нормальний

Copy link
Copy Markdown
Member

@Fundi1330 Fundi1330 left a comment

Choose a reason for hiding this comment

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

Нормально в цілому. Але я зараз не вдома, тому не запуска пр

Comment thread .env.example Outdated
Comment on lines +7 to +13
VITE_FIREBASE_API_KEY="AIzaSyA0yV1uvA2ciTRYyed8XL9HgBbhnyouGNE"
VITE_FIREBASE_AUTH_DOMAIN="star-8a692.firebaseapp.com"
VITE_FIREBASE_PROJECT_ID="star-8a692"
VITE_FIREBASE_STORAGE_BUCKET="star-8a692.firebasestorage.app"
VITE_FIREBASE_MESSAGING_SENDER_ID="221685619212"
VITE_FIREBASE_APP_ID="1:221685619212:web:06d14cefaa600a7e3dec29"
VITE_FIREBASE_MEASUREMENT_ID="G-3J22BCL1QQ"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

А чого тут реальні дані?

Comment thread frontend/index.html Outdated
Comment on lines +15 to +29
<!-- 1. Скрипт миттєвої теми -->
<script>
try {
if (
localStorage.getItem("theme") === "dark" ||
(!localStorage.getItem("theme") &&
window.matchMedia("(prefers-color-scheme: dark)")
.matches)
) {
document.documentElement.classList.add("dark");
}
} catch (_) {}
</script>

<!-- 2. Хардкодимо фон, щоб він малювався МИТТЄВО до завантаження CSS -->
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Краще коментарі залишати англійською

Comment thread frontend/src/App.tsx Outdated
Comment on lines +60 to +63
toast.error(
"Проблеми з сервером :(. Сповіщення тимчасово не працюють",
{ id: "socket-error" },
);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Чого тости не підгружаються з локалізації

Comment on lines +44 to +52
<span>
{bgText} ★ {bgText} ★&nbsp;
</span>
<span>
{bgText} ★ {bgText} ★&nbsp;
</span>
<span>
{bgText} ★ {bgText} ★&nbsp;
</span>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Краще не використовувати такі юнікод символи. Заміни їх на іконки

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Зауваження все ще залишається в силі

@Fundi1330
Copy link
Copy Markdown
Member

Не розумію прикола випадкового турніру, але хай буде вже, якщо воно готове. Також треба буде переписати слайдер турнірів під якусь бібліотеку для кращої стабільності. Ну і доробити решту сторінок, щоб інтернаціоналізація була і там

@izachoc izachoc requested review from AnnPoshtak and Fundi1330 May 11, 2026 15:10
@Fundi1330
Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Fundi1330
Copy link
Copy Markdown
Member

@AnnPoshtak, можеш підправити тести, щоб вони проходили?

@AnnPoshtak
Copy link
Copy Markdown
Collaborator

@AnnPoshtak, можеш підправити тести, щоб вони проходили?

Так, звичайно. Я майже це зробила, там зі скрін тестами біда

Copy link
Copy Markdown
Collaborator

@AnnPoshtak AnnPoshtak left a comment

Choose a reason for hiding this comment

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

Все чудвово, можна мерджити. Єдине що, кнопка мови скакає і на деяких сторінках не видно текст у режимі темної теми. але це можна вже і у деві пофіксити

2026-05-11.18-32-05.mp4

Comment thread frontend/src/components/ui/BrandingPanel.tsx
return (
<div
role="group"
aria-label="Вибір мови"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Оце теж мабуть має перекладатися

Comment thread frontend/src/components/Footer.tsx Outdated
import { Link } from "react-router-dom";
import { useTranslation } from "react-i18next";

const PLATFORM_LINKS = [
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Думаю краще це винести в конфіг файла, наприклад app_config.json. Так все буде в однмоу місці і буде зручно налаштовувати додаток

Comment thread frontend/src/components/Header.tsx Outdated
Comment on lines +13 to +20
const NAV_ITEMS = [
{ path: "/tournaments", key: "nav.tournaments" },
{ path: "/about-us", key: "nav.about" },
{ path: "/support", key: "nav.support" },
{ path: "/contact", key: "nav.contact" },
{ path: "/news", key: "nav.news" }, // Додано новий маршрут з гілки dev
];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Те саме. Краще винести в конфіг

Comment thread frontend/src/components/Header.tsx Outdated

{user?.uid ? (
<div onClick={() => setIsMobileMenuOpen(false)}>
{/* Додано userRoles та isMobile з гілки dev */}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Прошу коменти писати англійською. Переклади усі, будь ласка

Comment thread frontend/src/pages/Home/Home.tsx Outdated
Comment on lines 89 to 93
circularText: t(
"hero.mascot.circular",
"★ЗНАЙДИ КОМАНДУ ★ ПРОЯВИ СЕБЕ ",
),
lottieSrc: "/star.json",
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Так само, зірочки тут мають бути або іконкою, або їх не має бути

Comment on lines +203 to +217
Telegram: () => (
<svg
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
>
<line x1="22" y1="2" x2="11" y2="13" />
<polygon points="22 2 15 22 11 13 2 9 22 2" />
</svg>
),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Як на мене всі ці іконки можна імпортувати з якоїсь бібліотеки, як-от remix-icons, а не хардкодити

Comment on lines +29 to +59
{t("step3.title", "Підтвердження")}
</h1>
<p className="text-[15px] text-text-muted font-medium mb-6 transition-colors">
{t("step3.subtitle", "Перевірте дані перед реєстрацією.")}
</p>

<div className="bg-bg-card border-2 border-border rounded-[24px] overflow-hidden mb-5 transition-colors shadow-sm">
<div className="bg-indigo-950 px-7 pt-7 pb-6 text-white relative overflow-hidden">
<div className="absolute right-[-10px] bottom-[-20px] font-quicksand text-[60px] font-extrabold text-white/[0.03] tracking-[-0.03em] pointer-events-none select-none uppercase">
UGalaxy
</div>
<div className="text-[10px] font-bold tracking-[0.12em] uppercase opacity-60 mb-1.5 font-quicksand relative z-10">
{cfg.title}
</div>
<div className="font-quicksand font-extrabold text-[24px] tracking-[-0.01em] leading-tight relative z-10">
{formValues.teamName || capName}
</div>
</div>

<div className="px-7 py-2">
<div className="py-4 border-b border-border last:border-b-0 transition-colors">
<div className="flex items-center gap-3 mb-1">
<div className="text-primary">
<Icon.User />
</div>
<div className="text-[11px] font-bold tracking-[0.08em] uppercase text-text-muted font-quicksand">
{isSolo
? t("step3.labels.player", "Гравець")
: t("step3.labels.captain", "Капітан")}
</div>
</div>
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Так само не зрозуміло, чому тут є альтернативний переклад

) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("validation.teamNameRequired", "Введіть назву команди"),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Чому в одному місці ключі перекладу у lower_snake_case, а тут camelCase?

Comment on lines +20 to +23
{ id: "draft", dotColor: "bg-amber-500" },
{ id: "registration", dotColor: "bg-green-500" },
{ id: "running", dotColor: "bg-pink-accent" },
{ id: "finished", dotColor: "bg-text-muted" },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Оце можна в конфіг запихнути. Можеш глянути app_config.json, там вже є щось подібне

@Fundi1330
Copy link
Copy Markdown
Member

Жовтим позначив проблемні моменти, як правило відсутність перекладу.
image
image
image
А ще таке відчуття, наче профіль обрізаний хедером
image
Також terms of use веде на rules чомусь.
image
Ще треба додати сторінку на запит ролі в хедер обов'язково
Ну і можливо я би якось додав принаймні кнопку для зміни теми і мови на сторінки авторизації. А то якось незручно туди сюди бігати, щоб змінити мову. Я розумію, що воно трохи імерсивність псує, але спробуй якось це обіграти
image
image
image
Також має що там, що там бути s в кінці. Ну і зроби, щоб картки турнірів перекидали на сторінки турнірів
А так все нормально. Виглядає сайт просто казково

@Fundi1330
Copy link
Copy Markdown
Member

Можеш сам переклад, де його немає, завайбкодити. Так буде швидше

@Fundi1330
Copy link
Copy Markdown
Member

А чому для української і англійської шрифти різні?

@izachoc
Copy link
Copy Markdown
Member Author

izachoc commented May 11, 2026

А чому для української і англійської шрифти різні?

Бо треба знайти альтернативний шрифт, цей не підтримує кирилицю, але я не шукав поки

@izachoc izachoc requested a review from Fundi1330 May 11, 2026 22:25
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 31

Caution

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

⚠️ Outside diff range comments (2)
frontend/src/pages/Auth/AuthPage.tsx (1)

23-41: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update Zod schema to use v4 unified error customization syntax.

The current schema uses Zod v3 syntax for error messages (passing strings directly to .email() and .min()), which is deprecated in Zod v4. The code works but should be updated. At least three instances exist: two in frontend/src/pages/Auth/AuthPage.tsx (lines 27–28) and one in frontend/src/pages/Auth/ForgotPassword.tsx (line 18).

Replace the old syntax with Zod v4's error parameter:

🔧 Proposed fix for Zod v4 syntax
-    email: z.string().email("errors.email_invalid"),
-    password: z.string().min(8, "errors.pass_short"),
+    email: z.string().email({ error: "errors.email_invalid" }),
+    password: z.string().min(8, { error: "errors.pass_short" }),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Auth/AuthPage.tsx` around lines 23 - 41, The Zod schema
uses deprecated v3 error syntax; update the validators to Zod v4's error option
object: change email(...) to email({ message: "errors.email_invalid" }) and
password.min(8, "errors.pass_short") to password.min(8, { message:
"errors.pass_short" }); keep the superRefine custom issue for displayName as-is.
Apply the same update for the email validator in
frontend/src/pages/Auth/ForgotPassword.tsx (the email z.string() there) so all
three instances use the { message: ... } form.
frontend/src/App.tsx (1)

86-97: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden auth-token socket bootstrap against async races and token fetch failures.

This callback can create a stale socket when auth state changes quickly. The getIdToken() call at line 88 lacks error handling—if token fetch fails, the promise rejection will crash the callback and leave the socket disconnected. Additionally, without a run-guard, if the auth state changes while getIdToken() is pending, the old callback's resolved token could overwrite a newer socket connection.

Add a run ID counter to guard against stale callbacks, wrap the token fetch in try/catch, and check the run ID before connecting to ensure only the latest auth state's socket is used.

💡 Suggested fix
 useEffect(() => {
   let currentSocket: Socket | null = null;
+  let authRunId = 0;

   const unsubscribeAuth = onIdTokenChanged(auth, async (user) => {
+    const runId = ++authRunId;
     if (currentSocket) {
       currentSocket.disconnect();
+      currentSocket = null;
       setSocket(null);
     }

     if (user) {
-      const token = await user.getIdToken();
-      currentSocket = io(import.meta.env.VITE_SOCKETIO_SERVER_URL, {
-        auth: { token },
-        reconnectionDelay: 5000,
-      });
+      try {
+        const token = await user.getIdToken();
+        if (runId !== authRunId) return;
+        currentSocket = io(import.meta.env.VITE_SOCKETIO_SERVER_URL, {
+          auth: { token },
+          reconnectionDelay: 5000,
+        });
+      } catch {
+        return;
+      }

       currentSocket.on("connect_error", () => {
         // ...
       });

       currentSocket.on("connect", () => {
         // ...
       });

       setSocket(currentSocket);
     }
   });

   return () => {
+    authRunId++;
     unsubscribeAuth();
     if (currentSocket) currentSocket.disconnect();
   };
 }, [t]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` around lines 86 - 97, The onIdTokenChanged callback can
race and crash; introduce a local run counter (e.g., authRunId incremented each
callback) and capture the current runId at start of the async handler to ignore
stale completions, wrap the await user.getIdToken() in try/catch to
handle/recover from token fetch failures (log or return early without leaving
socket disconnected), and before creating a new socket with io(...) check that
the captured runId still matches the latest counter and that the auth state
hasn't changed; also ensure setSocket and currentSocket manipulation happen only
when the runId is still current to avoid overwriting a newer socket.
♻️ Duplicate comments (1)
frontend/src/pages/Auth/ForgotPassword.tsx (1)

17-19: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update Zod schema to use v4 unified error customization syntax.

Same issue as in AuthPage.tsx: the schema uses deprecated Zod v3 syntax for error messages.

🔧 Proposed fix for Zod v4 syntax
 const resetSchema = z.object({
-  email: z.string().email("errors.email_invalid"),
+  email: z.string().email({
+    error: () => "errors.email_invalid"
+  }),
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Auth/ForgotPassword.tsx` around lines 17 - 19, The Zod
schema resetSchema uses deprecated v3-style error strings; update the email
validator to Zod v4 unified error syntax by replacing the positional string with
an options object (set message:"errors.email_invalid") on the z.string().email()
call so resetSchema = z.object({ email: z.string().email({ message:
"errors.email_invalid" }) }); ensure any other validators in this file follow
the same pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/package.json`:
- Around line 25-46: Update the i18next-http-backend dependency in package.json
from "i18next-http-backend": "^3.0.4" to at least "^3.0.5" to pull in the patch
that fixes the path traversal/URL injection issue; run your package manager
(npm/yarn/pnpm) to update the lockfile (package-lock.json / yarn.lock /
pnpm-lock.yaml) and ensure CI installs the updated version before merging.

In `@frontend/src/App.tsx`:
- Around line 83-84: The socket effect currently depends on the translation
function `t`, causing socket tear-down/reconnect on language changes; decouple
them by storing `t` in a ref (e.g., `const tRef = useRef(t)`) and updating
`tRef.current = t` in a separate effect, then remove `t` from the socket
`useEffect` dependency array so `useEffect(() => { let currentSocket... },
[...deps without t])` manages socket lifecycle stably; update any places inside
the socket handlers to call `tRef.current(...)` instead of `t(...)`; apply the
same ref approach to the other effect mentioned (the one at line ~141) that
currently depends on `t`.

In `@frontend/src/components/Header.tsx`:
- Line 126: The mobile menu button in Header.tsx currently removes the keyboard
focus indicator via "focus:outline-none"; restore an accessible focus style by
removing that utility and adding a visible focus replacement (e.g., Tailwind
focus-visible:ring or focus:ring and focus-visible:ring-offset classes) on the
button element so keyboard users see a clear focus outline when interacting with
the mobile menu button.
- Around line 175-180: The mobile menu's parent wrapper currently closes the
menu on any click because of the div with onClick={() =>
setIsMobileMenuOpen(false)}, which prevents interacting with ProfileDropdown;
fix this by preventing event propagation for the dropdown area or moving the
close handler to the backdrop/explicit close control: replace the wrapping div
around ProfileDropdown with a container that stops propagation (e.g., onClick={e
=> e.stopPropagation()}) or remove the onClick entirely and attach
setIsMobileMenuOpen(false) to the backdrop/close button instead; update
references around setIsMobileMenuOpen and the ProfileDropdown usage in
Header.tsx accordingly.

In `@frontend/src/components/layouts/AuthLayout.tsx`:
- Line 10: The import in AuthLayout.tsx uses a brittle relative path for
starAnimation (import starAnimation from "../../../public/star.json"); move
star.json into src/assets and update the import to reference the new location or
use the project alias (e.g., "@/assets/star.json") so the component imports
starAnimation from src/assets via the alias; update any build/tsconfig paths if
needed and ensure AuthLayout.tsx's import statement references the new path.

In `@frontend/src/components/NotificationsDropdown.tsx`:
- Around line 19-21: The NotificationsDropdown trigger button is missing
accessible state and focus-visible styling; update the button rendered in
NotificationsDropdown.tsx (the element using onClick={toggleMenu}) to include
aria-expanded={isOpen} and aria-controls pointing to the dropdown panel's id,
replace the hardcoded label with the localized string used elsewhere (e.g.
t('notifications.buttonLabel') or the component's i18n key), and remove the
unconditional focus:outline-none in favor of a visible keyboard focus style such
as focus-visible:ring or focus-visible:outline to preserve keyboard focus
indication; apply the same aria and focus-visible changes to the other trigger
button in the file (the one around line 45).

In `@frontend/src/components/ScrollToTop.tsx`:
- Around line 7-9: The current useEffect in ScrollToTop calls window.scrollTo({
top: 0, behavior: "smooth" }) on pathname changes causing slow smooth scroll;
change it to an instant scroll by using window.scrollTo({ top: 0, behavior:
"auto" }) or simply window.scrollTo(0, 0) inside the same useEffect that depends
on pathname (refer to the useEffect hook and the pathname variable) so route
changes jump immediately to the top.

In `@frontend/src/components/ui/LanguageSwitcher.tsx`:
- Around line 36-40: The motion.div in LanguageSwitcher uses a fixed layoutId
("activeLangBg") which causes Framer Motion conflicts when multiple
LanguageSwitcher instances render; update the component to generate or accept a
unique id (e.g., via React's useId() or an instanceId prop) and interpolate it
into the layoutId (e.g., `activeLangBg-${id}`) so each LanguageSwitcher instance
supplies a unique layoutId for the motion.div to avoid shared animation
contexts.

In `@frontend/src/components/ui/Ticket3D.tsx`:
- Around line 168-188: The key for the animated member rows uses the unstable
array index in members.map (key={i}) which can break reconciliation/animations;
change the key on the motion.div to a stable identifier from the member object
such as m.name or a composite like `${m.name}-${i}` (use members.map((m, i) =>
...) and replace key={i} with key={m.name} or key={`${m.name}-${i}`}) so React
can correctly track additions/removals during edits.

In `@frontend/src/hooks/useClickOutside.ts`:
- Around line 7-22: The effect re-registers listeners whenever the handler
identity changes; instead capture the latest handler in a stable ref (e.g.,
create handlerRef, update handlerRef.current = handler inside an effect) and
have listener call handlerRef.current(event) so the useEffect for
adding/removing listeners depends only on ref (and not handler), or—if you can
target React 19.2—replace handler with useEffectEvent to get a stable event
callback and keep the effect dependency minimal; update the hook so useEffect
registers/removes listener only when ref changes and the listener delegates to
the stable handlerRef/useEffectEvent.

In `@frontend/src/locales/en/common.json`:
- Line 49: The aria-label string for the language switcher is grammatically
incorrect; update the "aria_label" value inside the "language_switcher" object
in frontend/src/locales/en/common.json (the "language_switcher" -> "aria_label"
key) to a correct phrase such as "Choose language" or "Language selection" so
the accessibility label reads properly.

In `@frontend/src/locales/en/tournaments.json`:
- Around line 19-22: The English locale file currently defines unnecessary
Slavic plural keys (tournaments_zero, tournaments_few, tournaments_many);
replace these with a single plural entry using only "one" and "other" (e.g., a
"tournaments" key with nested "one" and "other" forms) so i18next can handle
English plurals, and update callers to use i18next's pluralization API like
t('results.tournaments', { count: n }) where relevant; target the tournament
keys in frontend/src/locales/en/tournaments.json (remove
tournaments_zero/tournaments_few/tournaments_many and keep tournaments_one plus
tournaments_other under a single "tournaments" namespace).

In `@frontend/src/pages/Auth/AuthPage.tsx`:
- Line 181: The rendered error messages currently cast
errors.displayName.message (and likewise errors.*.message at the other
occurrences) directly to string which is unsafe; update the rendering to guard
the value before calling t() by checking that errors.displayName?.message is a
non-empty string (e.g. typeof message === "string" && message.trim()) and
otherwise pass a fallback key or safe string to t(); apply the same change for
the other occurrences referenced (the other errors.*.message uses around the
t(...) calls on the same file).
- Line 289: Wrap the direct call to signInWithPopup(auth, google) in a try/catch
or promise .catch inside the AuthPage onClick handler so sign-in failures (popup
blocked, user cancelled, network errors) are handled; locate the onClick that
currently calls signInWithPopup and replace it with an async handler (or attach
.catch) that logs the error and surfaces a user-friendly message (e.g., set an
error state or call showToast) using existing UI error-handling utilities so the
promise rejection is no longer unhandled.

In `@frontend/src/pages/Contact/Contact.tsx`:
- Around line 174-176: The footer text in the Contact component is hardcoded;
replace the literal "Star for Life Ukraine © 2026" in the div with a localized
string (e.g., use the existing i18n system: import and use useTranslation in the
Contact component and replace the content with t('contact.footerCopyright', {
year: new Date().getFullYear() }) or similar), and add the corresponding key
(contact.footerCopyright) to your locale files for English and Ukrainian (use a
template like "Star for Life Ukraine © {{year}}"). Ensure you update the JSX in
the div with className "mt-12 pt-6 border-t border-white/10 text-slate-400
text-sm italic" to render the t(...) result.

In `@frontend/src/pages/FaqPage/FaqPage.tsx`:
- Line 11: The faqIndexes array is hardcoded to 10; change it to derive its
length from the translations or a config and validate keys exist: fetch the FAQ
items via react-i18next (e.g. const items = i18n.t('faq.items', { returnObjects:
true }) || []) and then build faqIndexes = items.map((_, i) => i + 1) (or import
FAQ_CONFIG.itemCount if you prefer a config approach), and add a simple guard
that skips or logs missing question/answer entries when rendering (reference
faqIndexes, 'faq.items' translation key and any render function/component that
maps over faqIndexes).

In `@frontend/src/pages/Home/components/TournamentSlider.tsx`:
- Around line 228-237: The left navigation button in the TournamentSlider
component is an icon-only control and lacks accessible labeling and an explicit
button type; update the <button> that calls handlePrev (the one rendering
ArrowLeft) to include type="button" and a descriptive aria-label (e.g.,
aria-label="Previous tournament"), and make the same change for the matching
right navigation button (the one rendering ArrowRight/handleNext) so both are
keyboard/screen-reader accessible.
- Around line 118-177: The startRoulette function schedules an interval and
requestAnimationFrame but doesn't track or clear them on unmount; add two refs
rouletteIntervalRef and rouletteRafRef, assign the interval ID returned by
setInterval to rouletteIntervalRef.current and assign the requestAnimationFrame
id returned by requestAnimationFrame to rouletteRafRef.current, and ensure you
clearInterval(rouletteIntervalRef.current) and
cancelAnimationFrame(rouletteRafRef.current) whenever you stop the roulette
(after clearing the interval and when the final animation completes) and inside
a useEffect cleanup to avoid stale updates; also handle the early exit when
cards.length === 0 by clearing any timers and calling setIsSpinning(false) so
the component doesn't stay stuck spinning.

In `@frontend/src/pages/Home/Home.tsx`:
- Around line 8-15: The NotoEmoji image has no error/loading handling and uses a
non-descriptive alt; update the NotoEmoji component to accept an optional
descriptive label prop (e.g., altText) or derive a human-readable alt from the
code, render a loading state (e.g., small spinner or placeholder) while the
image loads, and add an onError handler to swap the src to a local/fallback SVG
or render a fallback element when the CDN fails; ensure the img uses the
descriptive alt (or aria-hidden if purely decorative). Locate the NotoEmoji
function and update its props, add onLoad/onError handlers and fallback logic,
and then update any calls to NotoEmoji/badge usages to pass the new
altText/label where appropriate.

In `@frontend/src/pages/Page404/Page404.tsx`:
- Around line 59-68: The continuous sticker animation in the useEffect that
creates the interval should respect the user's reduced-motion preference: detect
prefers-reduced-motion (e.g., via window.matchMedia('(prefers-reduced-motion:
reduce)').matches or a custom hook) before starting the interval in the effect
that calls setStickers and generateRandomSticker, and skip creating the interval
entirely when reduced motion is requested (returning a no-op cleanup). Apply the
same guard to the other effect block (the one around setStickers at the 77-84
area) so neither interval-driven sticker churn nor repeated updates run for
users who opt out of animations.
- Around line 7-11: The emoji <img> in Page404 (the element rendering
src={`https://fonts.gstatic.com/s/e/notoemoji/latest/${code}/emoji.svg`} with
className="w-6 h-6 select-none") is decorative and should be hidden from
assistive tech; remove the meaningful alt text and mark it as presentation by
setting an empty alt and adding aria-hidden="true" (and optionally
role="presentation") so screen readers do not announce it.

In `@frontend/src/pages/RegistrationPage/components/Icons.tsx`:
- Around line 16-31: Replace the repetitive explicit property assignments in the
Icon object with ES6 property shorthand for properties where the key and value
identifiers are identical (e.g., change "Check: Check" to "Check"). Keep
explicit mappings where the key differs from the imported identifier (e.g.,
"Warning: AlertTriangle", "Team: Users", "Solo: User", "Org: Home", "Telegram:
Send") so only entries like Check, ChevronRight, ChevronLeft, ChevronDown, Plus,
X, Info, User, and Users use shorthand in the exported Icon object.

In `@frontend/src/pages/RegistrationPage/components/StepGeneral.tsx`:
- Around line 36-39: handleNextClick currently opens the accordion when
errors.customFields exists but still calls onNext(), allowing navigation despite
validation errors; update handleNextClick so that if errors.customFields is
truthy and optOpen is false you call setOptOpen(true) and immediately return
(preventing onNext from running), otherwise proceed to call onNext(); reference
the handleNextClick function and the variables/errors.customFields, optOpen,
setOptOpen, and onNext when making the change.
- Line 227: Remove the unsafe "as any" on the register call for customFields and
replace it with a type-safe path: define the form value type that includes
customFields (e.g., RegistrationFormValues with customFields keyed by a union of
known IDs), then cast the template literal to the proper Path type from
react-hook-form (or to a string literal union like `CustomFieldPath =
\`customFields.${KnownCustomFields}\``) when calling
register(`customFields.${field.id}`) in StepGeneral.tsx so you preserve
type-safety for register, field.id, and the customFields shape.

In `@frontend/src/pages/RegistrationPage/components/StepMembers.tsx`:
- Around line 36-38: The teammate bounds logic in StepMembers.tsx sets
maxTeammates = Math.max(0, cfg.maxMembers - 1) but forces minTeammates =
Math.max(1, cfg.minMembers - 1), which breaks when cfg.maxMembers === 1 (min >
max and progress/division by zero). Update both places (the initial definition
and the other occurrences around lines 92-93 and 241-247) to compute safe
non-negative bounds: use Math.max(0, cfg.minMembers - 1) and Math.max(0,
cfg.maxMembers - 1), then clamp minTeammates = Math.min(minTeammates,
maxTeammates) (or vice versa) so min ≤ max and handle the zero-teammate case
gracefully (disable Add/Next or treat range as 0..0) in the UI logic in
StepMembers component.
- Around line 39-63: handleNextClick currently bypasses form validation and can
call onNext() with invalid fields; make it asynchronous and explicitly trigger
form validation for the member group and captainEmail before proceeding. Inside
handleNextClick, await the form library's trigger (e.g., trigger("members") and
trigger("captainEmail") or a single trigger for all relevant fields), and if
trigger returns false, setMembersError with a suitable message and return; only
run the existing duplicate/count checks and call onNext() when validation
passed. Ensure you update the function signature to async and reference existing
symbols: handleNextClick, trigger, getValues, fields, captainEmail,
minTeammates, setMembersError, and onNext.

In `@frontend/src/pages/RegistrationPage/components/StepSuccess.tsx`:
- Line 1: Remove the unused React namespace import and any usage of the React.FC
type: delete the top-level `import React from "react";`, update the component
definition `StepSuccess` to a plain function or arrow function without
`React.FC` (e.g., `export default function StepSuccess(...) { ... }` or `const
StepSuccess = (...) => { ... }; export default StepSuccess;`), and ensure any
JSX remains unchanged so the file relies on the new JSX transform.

In `@frontend/src/pages/RegistrationPage/RegistrationPage.tsx`:
- Around line 24-31: The hardcoded user object in RegistrationPage.tsx (variable
user) should be replaced with the authenticated Firebase user: read
auth.currentUser (same pattern used in Profile, JuryPanel, GetRole) and map its
properties (displayName, email, uid, etc.) into the form payload, falling back
to existing fields (telegram, github, discord) from local state or inputs if
needed; update any references to the old user variable to use this derived
currentUser object and handle null/unauthenticated cases (redirect or show login
prompt) accordingly.

In `@frontend/src/pages/RegistrationPage/types.ts`:
- Around line 24-32: The Zod calls in customFieldsShape[field.id] (e.g.,
z.string().min(1, ...) and any z.string().email(...) uses) are using the
deprecated legacy string error-parameter; update them to Zod 4's unified error
format by replacing the second positional string argument with an options object
(e.g., { error: ... } or { error: (issue) => t(...) } as needed) for the
z.string().min and z.string().email invocations so all custom field validators
use the new error parameter shape.

In `@frontend/src/pages/TournamentPage/TournamentPage.tsx`:
- Around line 394-396: The value prop in TournamentPage using a hardcoded
Ukrainian prefix ("До ") breaks localization; replace the string interpolation
value={`До ${tournament.max_teams}`} with a translated string that accepts the
number (e.g., use the t function with an interpolation key such as
t("stats.upTo", { count: tournament.max_teams }) or a suitable
pluralized/templated key) so the prefix is produced by the i18n lookup rather
than hardcoded; update the value expression to call t(...) and pass
tournament.max_teams as the interpolation variable.

In `@frontend/src/pages/TournamentsPage/TournamentsPage.tsx`:
- Around line 121-127: Replace the manual ternary that chooses singular/plural
with i18next pluralization: pass filteredData.length as the count to t (e.g.,
t("results.tournaments", { count: filteredData.length })) in the TournamentsPage
JSX instead of using filteredData.length === 1 ? ... : ...; also update the
translations to provide plural forms for the key "results.tournaments" using the
_one and _other suffixes (e.g., "results.tournaments_one" and
"results.tournaments_other") so i18next can resolve the correct form.

---

Outside diff comments:
In `@frontend/src/App.tsx`:
- Around line 86-97: The onIdTokenChanged callback can race and crash; introduce
a local run counter (e.g., authRunId incremented each callback) and capture the
current runId at start of the async handler to ignore stale completions, wrap
the await user.getIdToken() in try/catch to handle/recover from token fetch
failures (log or return early without leaving socket disconnected), and before
creating a new socket with io(...) check that the captured runId still matches
the latest counter and that the auth state hasn't changed; also ensure setSocket
and currentSocket manipulation happen only when the runId is still current to
avoid overwriting a newer socket.

In `@frontend/src/pages/Auth/AuthPage.tsx`:
- Around line 23-41: The Zod schema uses deprecated v3 error syntax; update the
validators to Zod v4's error option object: change email(...) to email({
message: "errors.email_invalid" }) and password.min(8, "errors.pass_short") to
password.min(8, { message: "errors.pass_short" }); keep the superRefine custom
issue for displayName as-is. Apply the same update for the email validator in
frontend/src/pages/Auth/ForgotPassword.tsx (the email z.string() there) so all
three instances use the { message: ... } form.

---

Duplicate comments:
In `@frontend/src/pages/Auth/ForgotPassword.tsx`:
- Around line 17-19: The Zod schema resetSchema uses deprecated v3-style error
strings; update the email validator to Zod v4 unified error syntax by replacing
the positional string with an options object (set
message:"errors.email_invalid") on the z.string().email() call so resetSchema =
z.object({ email: z.string().email({ message: "errors.email_invalid" }) });
ensure any other validators in this file follow the same pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: aac7d6ce-ec9c-4fc9-bc5e-1219602f4e1c

📥 Commits

Reviewing files that changed from the base of the PR and between 63d2c6c and 22e61a6.

⛔ Files ignored due to path filters (3)
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/src/pages/Auth/__snapshots__/Auth.test.tsx.snap is excluded by !**/*.snap
  • frontend/src/pages/Auth/__snapshots__/ForgotPassword.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (69)
  • frontend/index.html
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/components/Footer.tsx
  • frontend/src/components/Header.tsx
  • frontend/src/components/Hero.tsx
  • frontend/src/components/MainLayout.tsx
  • frontend/src/components/NotificationsDropdown.tsx
  • frontend/src/components/ProfileDropdown.tsx
  • frontend/src/components/ScrollToTop.tsx
  • frontend/src/components/ThemeToggle.tsx
  • frontend/src/components/TournamentCard.tsx
  • frontend/src/components/icons/BrandIcons.tsx
  • frontend/src/components/layouts/AuthLayout.tsx
  • frontend/src/components/ui/BrandingPanel.tsx
  • frontend/src/components/ui/Button.tsx
  • frontend/src/components/ui/LanguageSwitcher.tsx
  • frontend/src/components/ui/Ticket3D.tsx
  • frontend/src/components/ui/index.ts
  • frontend/src/config/navigation.ts
  • frontend/src/data/mockTournaments.ts
  • frontend/src/firebase.ts
  • frontend/src/hooks/useClickOutside.ts
  • frontend/src/i18n/config.ts
  • frontend/src/index.css
  • frontend/src/locales/en/about.json
  • frontend/src/locales/en/auth.json
  • frontend/src/locales/en/common.json
  • frontend/src/locales/en/contact.json
  • frontend/src/locales/en/faq.json
  • frontend/src/locales/en/home.json
  • frontend/src/locales/en/registration.json
  • frontend/src/locales/en/rules.json
  • frontend/src/locales/en/support.json
  • frontend/src/locales/en/tournaments.json
  • frontend/src/locales/uk/about.json
  • frontend/src/locales/uk/auth.json
  • frontend/src/locales/uk/common.json
  • frontend/src/locales/uk/contact.json
  • frontend/src/locales/uk/faq.json
  • frontend/src/locales/uk/home.json
  • frontend/src/locales/uk/registration.json
  • frontend/src/locales/uk/rules.json
  • frontend/src/locales/uk/support.json
  • frontend/src/locales/uk/tournaments.json
  • frontend/src/main.tsx
  • frontend/src/pages/AboutUs/AboutUs.tsx
  • frontend/src/pages/Auth/AuthPage.tsx
  • frontend/src/pages/Auth/ForgotPassword.tsx
  • frontend/src/pages/Contact/Contact.tsx
  • frontend/src/pages/FaqPage/FaqPage.tsx
  • frontend/src/pages/Home/Home.tsx
  • frontend/src/pages/Home/components/TournamentSlider.tsx
  • frontend/src/pages/Page404/Page404.tsx
  • frontend/src/pages/RegistrationPage/RegistrationPage.tsx
  • frontend/src/pages/RegistrationPage/components/FormUI.tsx
  • frontend/src/pages/RegistrationPage/components/Icons.tsx
  • frontend/src/pages/RegistrationPage/components/StepConfirm.tsx
  • frontend/src/pages/RegistrationPage/components/StepGeneral.tsx
  • frontend/src/pages/RegistrationPage/components/StepMembers.tsx
  • frontend/src/pages/RegistrationPage/components/StepSuccess.tsx
  • frontend/src/pages/RegistrationPage/components/StepsNav.tsx
  • frontend/src/pages/RegistrationPage/types.ts
  • frontend/src/pages/Rules/Rules.tsx
  • frontend/src/pages/SupportPage/SupportPage.tsx
  • frontend/src/pages/TournamentPage/TournamentPage.tsx
  • frontend/src/pages/TournamentsPage/TournamentsPage.tsx
  • frontend/src/routers/Router.tsx
  • frontend/src/utils/cn.ts
💤 Files with no reviewable changes (1)
  • frontend/src/firebase.ts

Comment thread frontend/package.json
Comment on lines +25 to 46
"clsx": "^2.1.1",
"firebase": "^12.10.0",
"framer-motion": "^12.38.0",
"i18next": "^26.0.5",
"i18next-browser-languagedetector": "^8.2.1",
"i18next-http-backend": "^3.0.4",
"lottie-react": "^2.4.1",
"lucide-react": "^1.7.0",
"lucide-react": "^1.8.0",
"react": "^19.2.0",
"react-dom": "^19.2.0",
"react-hook-form": "^7.72.0",
"react-hot-toast": "^2.6.0",
"react-i18next": "^17.0.3",
"react-icons": "^5.6.0",
"react-redux": "^9.2.0",
"react-router-dom": "^7.13.1",
"react-toastify": "^11.1.0",
"socket.io-client": "^4.8.3",
"sonner": "^2.0.7",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.2.1",
"zod": "^4.3.6"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify package versions exist on npm and check for security advisories

packages=(
  "clsx@2.1.1"
  "i18next@26.0.5"
  "i18next-browser-languagedetector@8.2.1"
  "i18next-http-backend@3.0.4"
  "lucide-react@1.8.0"
  "react-i18next@17.0.3"
  "react-toastify@11.1.0"
  "tailwind-merge@3.5.0"
)

echo "Checking package versions..."
for pkg in "${packages[@]}"; do
  name=$(echo "$pkg" | cut -d'@' -f1)
  version=$(echo "$pkg" | cut -d'@' -f2)
  echo "Checking $name@$version..."
  
  # Check if version exists
  npm view "$name@$version" version 2>/dev/null
  if [ $? -ne 0 ]; then
    echo "  ❌ Version $version not found for $name"
  else
    echo "  ✅ Version exists"
  fi
done

echo -e "\nChecking for security advisories..."
for pkg in "${packages[@]}"; do
  name=$(echo "$pkg" | cut -d'@' -f1)
  echo "Checking $name..."
  gh api graphql -f query="
  {
    securityVulnerabilities(first: 5, ecosystem: NPM, package: \"$name\") {
      nodes {
        advisory {
          summary
          severity
          publishedAt
        }
        vulnerableVersionRange
        firstPatchedVersion {
          identifier
        }
      }
    }
  }
  " 2>/dev/null | jq '.data.securityVulnerabilities.nodes[] | select(.vulnerableVersionRange)' || echo "  No advisories found"
done

Repository: ugalaxy-team/Project

Length of output: 1608


Upgrade i18next-http-backend to version 3.0.5 or later—path traversal vulnerability detected.

The package i18next-http-backend@3.0.4 has a Path Traversal & URL Injection vulnerability via unsanitized lng/ns parameters (MODERATE severity). This is fixed in version 3.0.5. All other verified package versions exist and have no active security advisories.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/package.json` around lines 25 - 46, Update the i18next-http-backend
dependency in package.json from "i18next-http-backend": "^3.0.4" to at least
"^3.0.5" to pull in the patch that fixes the path traversal/URL injection issue;
run your package manager (npm/yarn/pnpm) to update the lockfile
(package-lock.json / yarn.lock / pnpm-lock.yaml) and ensure CI installs the
updated version before merging.

Comment thread frontend/src/App.tsx
Comment on lines 83 to 84
useEffect(() => {
let currentSocket: Socket | null = null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find . -name "App.tsx" -type f | head -20

Repository: ugalaxy-team/Project

Length of output: 87


🏁 Script executed:

cat -n frontend/src/App.tsx | head -150

Repository: ugalaxy-team/Project

Length of output: 5954


Decouple socket lifecycle from translation function identity.

The socket effect re-runs whenever t changes (on language switch), causing unnecessary disconnection and reconnection of the socket. Keep socket lifecycle stable by reading translations via a ref instead.

Suggested fix
 export const App = () => {
   const { t } = useTranslation("common");
+  const tRef = useRef(t);
+  useEffect(() => {
+    tRef.current = t;
+  }, [t]);

   useEffect(() => {
     let currentSocket: Socket | null = null;
     const unsubscribeAuth = onIdTokenChanged(auth, async (user) => {
       // ...
       currentSocket.on("connect_error", () => {
         toast.error(
           <div>
             <div className="font-bold mb-1">
-              {t("errors.socket", "Проблеми з сервером :(")}
+              {tRef.current("errors.socket", "Проблеми з сервером :(")}
             </div>
             {/* ... */}
           </div>,
         );
       });
     });
     return () => {
       unsubscribeAuth();
       if (currentSocket) currentSocket.disconnect();
     };
-  }, [t]);
+  }, []);

Also applies to: line 141

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` around lines 83 - 84, The socket effect currently
depends on the translation function `t`, causing socket tear-down/reconnect on
language changes; decouple them by storing `t` in a ref (e.g., `const tRef =
useRef(t)`) and updating `tRef.current = t` in a separate effect, then remove
`t` from the socket `useEffect` dependency array so `useEffect(() => { let
currentSocket... }, [...deps without t])` manages socket lifecycle stably;
update any places inside the socket handlers to call `tRef.current(...)` instead
of `t(...)`; apply the same ref approach to the other effect mentioned (the one
at line ~141) that currently depends on `t`.


<button
className="lg:hidden text-white p-2 focus:outline-none"
className="lg:hidden text-white p-1.5 focus:outline-none hover:text-accent transition-colors"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore visible keyboard focus on the mobile menu button.

focus:outline-none removes the only default focus indicator and no focus-visible replacement is provided.

Suggested fix
-            className="lg:hidden text-white p-1.5 focus:outline-none hover:text-accent transition-colors"
+            className="lg:hidden text-white p-1.5 hover:text-accent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg-header-bg"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
className="lg:hidden text-white p-1.5 focus:outline-none hover:text-accent transition-colors"
className="lg:hidden text-white p-1.5 hover:text-accent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 focus-visible:ring-offset-bg-header-bg"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/Header.tsx` at line 126, The mobile menu button in
Header.tsx currently removes the keyboard focus indicator via
"focus:outline-none"; restore an accessible focus style by removing that utility
and adding a visible focus replacement (e.g., Tailwind focus-visible:ring or
focus:ring and focus-visible:ring-offset classes) on the button element so
keyboard users see a clear focus outline when interacting with the mobile menu
button.

Comment on lines +175 to +180
<div onClick={() => setIsMobileMenuOpen(false)}>
<ProfileDropdown
userRoles={user?.roles || []}
isMobile={true}
/>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Mobile profile actions are effectively unreachable due to parent click-close.

The wrapping <div onClick={() => setIsMobileMenuOpen(false)}> closes the whole mobile menu on any interaction with ProfileDropdown, so users can’t reliably open/use its internal menu on mobile.

Suggested fix
-                {user?.uid ? (
-                  <div onClick={() => setIsMobileMenuOpen(false)}>
-                    <ProfileDropdown
-                      userRoles={user?.roles || []}
-                      isMobile={true}
-                    />
-                  </div>
+                {user?.uid ? (
+                  <ProfileDropdown
+                    userRoles={user?.roles || []}
+                    isMobile={true}
+                  />
                 ) : (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div onClick={() => setIsMobileMenuOpen(false)}>
<ProfileDropdown
userRoles={user?.roles || []}
isMobile={true}
/>
</div>
<ProfileDropdown
userRoles={user?.roles || []}
isMobile={true}
/>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/Header.tsx` around lines 175 - 180, The mobile menu's
parent wrapper currently closes the menu on any click because of the div with
onClick={() => setIsMobileMenuOpen(false)}, which prevents interacting with
ProfileDropdown; fix this by preventing event propagation for the dropdown area
or moving the close handler to the backdrop/explicit close control: replace the
wrapping div around ProfileDropdown with a container that stops propagation
(e.g., onClick={e => e.stopPropagation()}) or remove the onClick entirely and
attach setIsMobileMenuOpen(false) to the backdrop/close button instead; update
references around setIsMobileMenuOpen and the ProfileDropdown usage in
Header.tsx accordingly.

import { LanguageSwitcher } from "../ui/LanguageSwitcher";

import { BrandingPanel } from "../ui/BrandingPanel";
import starAnimation from "../../../public/star.json";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify the star.json file location and how it's referenced elsewhere

# Find star.json file
fd -t f "star.json"

# Check how star.json is imported in other files
rg -n "star\.json" --type=ts --type=tsx -C 2

Repository: ugalaxy-team/Project

Length of output: 173


🏁 Script executed:

# Check if this is a Vite project
cat frontend/package.json | head -50

Repository: ugalaxy-team/Project

Length of output: 1538


🏁 Script executed:

# Search for how public assets are imported in the codebase
rg -n "from.*public" frontend/src --max-count=20

Repository: ugalaxy-team/Project

Length of output: 169


🏁 Script executed:

# Look for vite config
fd -t f "vite.config" frontend/

Repository: ugalaxy-team/Project

Length of output: 88


🏁 Script executed:

# Check if there are other JSON imports from public directory
rg -n "\.json" frontend/src/components --max-count=15

Repository: ugalaxy-team/Project

Length of output: 169


🏁 Script executed:

cat frontend/vite.config.ts

Repository: ugalaxy-team/Project

Length of output: 634


🏁 Script executed:

# Check how starAnimation is used in AuthLayout.tsx
sed -n '1,50p' frontend/src/components/layouts/AuthLayout.tsx

Repository: ugalaxy-team/Project

Length of output: 1909


🏁 Script executed:

# Look for any TypeScript or build configuration that might affect JSON imports
cat frontend/tsconfig.json | head -60

Repository: ugalaxy-team/Project

Length of output: 183


Consider moving the Lottie JSON to src/assets or using path alias for better maintainability.

The import path ../../../public/star.json works correctly and resolves to frontend/public/star.json, but it's not idiomatic to Vite. The relative path creates fragility—if AuthLayout.tsx moves to a different directory depth, the path breaks. Instead:

  • Move star.json to src/assets/ and import with a relative path, or
  • Use the configured @ alias (e.g., import starAnimation from "@/assets/star.json") for consistency with the rest of the codebase

This aligns with Vite conventions and makes imports more maintainable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/layouts/AuthLayout.tsx` at line 10, The import in
AuthLayout.tsx uses a brittle relative path for starAnimation (import
starAnimation from "../../../public/star.json"); move star.json into src/assets
and update the import to reference the new location or use the project alias
(e.g., "@/assets/star.json") so the component imports starAnimation from
src/assets via the alias; update any build/tsconfig paths if needed and ensure
AuthLayout.tsx's import statement references the new path.

@@ -0,0 +1,48 @@
import React from "react";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unnecessary React import.

In React 19, the React namespace import is no longer required for JSX or component definitions. The React.FC type is also no longer recommended. You can remove this import entirely.

♻️ Simplify component definition for React 19
-import React from "react";
 import { motion } from "framer-motion";
 import { useTranslation } from "react-i18next";
 import { useFormContext } from "react-hook-form";
 import { Icon } from "./Icons";
 import { BtnNext } from "./FormUI";
 import type { RegFormData } from "../types";

 interface StepSuccessProps {
   onHome: () => void;
 }

-export const StepSuccess: React.FC<StepSuccessProps> = ({ onHome }) => {
+export const StepSuccess = ({ onHome }: StepSuccessProps) => {
   const { t } = useTranslation("registration");
   const { watch } = useFormContext<RegFormData>();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/RegistrationPage/components/StepSuccess.tsx` at line 1,
Remove the unused React namespace import and any usage of the React.FC type:
delete the top-level `import React from "react";`, update the component
definition `StepSuccess` to a plain function or arrow function without
`React.FC` (e.g., `export default function StepSuccess(...) { ... }` or `const
StepSuccess = (...) => { ... }; export default StepSuccess;`), and ensure any
JSX remains unchanged so the file relies on the new JSX transform.

Comment on lines +24 to +31
const user = {
displayName: "izachoc",
full_name: "Андрій Іванченко",
email: "andriy.test@gmail.com",
telegram: "@izachoc_dev",
github: "izachoc-code",
discord: "izachoc#1234",
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for auth context or user hook in the codebase
echo "=== Searching for auth context providers ==="
rg -n "AuthContext|UserContext|useAuth|useUser" --type=ts --type=tsx frontend/src -A 2

echo -e "\n=== Checking if Firebase auth user is available ==="
rg -n "auth.currentUser|onAuthStateChanged" --type=ts --type=tsx frontend/src

Repository: ugalaxy-team/Project

Length of output: 236


🏁 Script executed:

#!/bin/bash
# Corrected search without problematic --type flags
echo "=== Searching for auth-related patterns ==="
rg -n "useAuth|AuthContext|UserContext" frontend/src -A 2

echo -e "\n=== Searching for Firebase auth ==="
rg -n "firebase.*auth|auth.*currentUser" frontend/src

echo -e "\n=== Checking RegistrationPage file ==="
fd "RegistrationPage.tsx" frontend/src

Repository: ugalaxy-team/Project

Length of output: 3232


🏁 Script executed:

#!/bin/bash
# Read the RegistrationPage.tsx file
file_path=$(fd "RegistrationPage.tsx" frontend/src 2>/dev/null | head -1)
if [ -n "$file_path" ]; then
  echo "=== Content of $file_path ==="
  cat -n "$file_path"
else
  echo "File not found, searching with broader pattern..."
  fd "RegistrationPage" frontend/src
fi

Repository: ugalaxy-team/Project

Length of output: 7746


🏁 Script executed:

#!/bin/bash
# Search for auth providers, hooks, context setup
echo "=== Looking for auth hook implementations ==="
fd "useAuth|auth" frontend/src --type f | head -20

echo -e "\n=== Looking for auth-related directories ==="
fd --type d "auth|hooks" frontend/src

Repository: ugalaxy-team/Project

Length of output: 269


Use authenticated user from Firebase auth instead of hardcoded test data.

The hardcoded test user will cause all registrations to be attributed to "izachoc" instead of the actual authenticated user. Firebase auth is already set up and used throughout the codebase (Profile, JuryPanel, GetRole pages); apply the same pattern here by accessing auth.currentUser directly.

🔧 Proposed fix: Use Firebase auth
import React, { useState } from "react";
import { useParams, Navigate, useNavigate, Link } from "react-router-dom";
import { useForm, FormProvider } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { motion, AnimatePresence } from "framer-motion";
import { useTranslation } from "react-i18next";
+import { auth } from "../../firebase";

import { TOURNAMENTS_DATA } from "../../data/mockTournaments";
import { BrandingPanel } from "../../components/ui/BrandingPanel";
import { Ticket3D } from "../../components/ui/Ticket3D";

import { makeSchema, type RegFormData, type TournamentConfig } from "./types";
import { StepsNav } from "./components/StepsNav";
import { StepGeneral } from "./components/StepGeneral";
import { StepMembers } from "./components/StepMembers";
import { StepConfirm } from "./components/StepConfirm";
import { StepSuccess } from "./components/StepSuccess";

export const RegistrationPage = () => {
  const { t } = useTranslation("registration");
  const navigate = useNavigate();
  const { id } = useParams();
-
-  const user = {
-    displayName: "izachoc",
-    full_name: "Андрій Іванченко",
-    email: "andriy.test@gmail.com",
-    telegram: "@izachoc_dev",
-    github: "izachoc-code",
-    discord: "izachoc#1234",
-  };
+
+  const fbUser = auth.currentUser;
+  if (!fbUser) return <Navigate to="/auth" replace />;
+
+  const user = {
+    displayName: fbUser.displayName || "",
+    full_name: fbUser.displayName || "",
+    email: fbUser.email || "",
+    telegram: "",
+    github: "",
+    discord: "",
+  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const user = {
displayName: "izachoc",
full_name: "Андрій Іванченко",
email: "andriy.test@gmail.com",
telegram: "@izachoc_dev",
github: "izachoc-code",
discord: "izachoc#1234",
};
import React, { useState } from "react";
import { useParams, Navigate, useNavigate, Link } from "react-router-dom";
import { useForm, FormProvider } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { motion, AnimatePresence } from "framer-motion";
import { useTranslation } from "react-i18next";
import { auth } from "../../firebase";
import { TOURNAMENTS_DATA } from "../../data/mockTournaments";
import { BrandingPanel } from "../../components/ui/BrandingPanel";
import { Ticket3D } from "../../components/ui/Ticket3D";
import { makeSchema, type RegFormData, type TournamentConfig } from "./types";
import { StepsNav } from "./components/StepsNav";
import { StepGeneral } from "./components/StepGeneral";
import { StepMembers } from "./components/StepMembers";
import { StepConfirm } from "./components/StepConfirm";
import { StepSuccess } from "./components/StepSuccess";
export const RegistrationPage = () => {
const { t } = useTranslation("registration");
const navigate = useNavigate();
const { id } = useParams();
const fbUser = auth.currentUser;
if (!fbUser) return <Navigate to="/auth" replace />;
const user = {
displayName: fbUser.displayName || "",
full_name: fbUser.displayName || "",
email: fbUser.email || "",
telegram: "",
github: "",
discord: "",
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/RegistrationPage/RegistrationPage.tsx` around lines 24 -
31, The hardcoded user object in RegistrationPage.tsx (variable user) should be
replaced with the authenticated Firebase user: read auth.currentUser (same
pattern used in Profile, JuryPanel, GetRole) and map its properties
(displayName, email, uid, etc.) into the form payload, falling back to existing
fields (telegram, github, discord) from local state or inputs if needed; update
any references to the old user variable to use this derived currentUser object
and handle null/unauthenticated cases (redirect or show login prompt)
accordingly.

Comment on lines +24 to +32
customFieldsShape[field.id] = z
.string()
.min(
1,
t(
"validation.requiredCustom",
`Поле "${field.label}" є обов'язковим`,
),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Verify Zod 4 error message syntax compatibility.

The code uses the legacy error message syntax:

z.string().min(1, "Error message")
z.string().email("Invalid email")

Zod 4 introduced a unified error parameter that accepts a callback. While Zod may maintain backward compatibility, you should verify that the legacy string syntax still works in Zod 4, or migrate to the new format:

z.string().min(1, {
  error: (issue) => t("validation.requiredCustom", `Поле обов'язкове`)
})

Migrate to Zod 4 unified error syntax.

The message parameter is still supported but deprecated in Zod 4. The code uses the legacy error message syntax which still works but should be migrated to the new unified error parameter:

Current (deprecated):

z.string().min(1, "Error message")
z.string().email("Invalid email")

Recommended Zod 4 syntax:

z.string().min(1, { error: "Error message" })
z.string().email({ error: "Invalid email" })
♻️ Proposed migration to Zod 4 syntax
      if (field.required) {
        customFieldsShape[field.id] = z
          .string()
-          .min(
-            1,
-            t(
-              "validation.requiredCustom",
-              `Поле "${field.label}" є обов'язковим`,
-            ),
-          );
+          .min(1, {
+            error: t(
+              "validation.requiredCustom",
+              `Поле "${field.label}" є обов'язковим`,
+            ),
+          });
      } else {
        z.object({
          name: z
            .string()
-            .min(2, t("validation.nameRequired", "Введіть ПІБ або нікнейм")),
+            .min(2, {
+              error: t("validation.nameRequired", "Введіть ПІБ або нікнейм"),
+            }),
          email: z
            .string()
-            .email(t("validation.invalidEmail", "Невірний email")),
+            .email({
+              error: t("validation.invalidEmail", "Невірний email"),
+            }),
          customFields: z.object(customFieldsShape).optional(),
        }),

Also applies to: 49-52

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/RegistrationPage/types.ts` around lines 24 - 32, The Zod
calls in customFieldsShape[field.id] (e.g., z.string().min(1, ...) and any
z.string().email(...) uses) are using the deprecated legacy string
error-parameter; update them to Zod 4's unified error format by replacing the
second positional string argument with an options object (e.g., { error: ... }
or { error: (issue) => t(...) } as needed) for the z.string().min and
z.string().email invocations so all custom field validators use the new error
parameter shape.

Comment thread frontend/src/pages/TournamentPage/TournamentPage.tsx Outdated
Comment on lines +121 to 127
<div className="mb-6 text-[15px] font-bold text-text-muted transition-colors duration-300 px-1">
{t("results.found")}{" "}
<strong className="text-primary">{filteredData.length}</strong>{" "}
турнірів
{filteredData.length === 1
? t("results.tournaments_one")
: t("results.tournaments_many")}
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Use i18next's built-in pluralization instead of manual ternary logic.

The current implementation uses a simple ternary to choose between singular and plural forms. react-i18next provides built-in pluralization support that handles more complex plural rules (including languages with multiple plural forms) and is more maintainable.

♻️ Refactor to use i18next pluralization

Update the translation files to use _one and _other suffixes:

frontend/src/locales/en/tournaments.json:

{
  "results": {
    "found": "Found",
    "tournaments_one": "tournament",
    "tournaments_other": "tournaments"
  }
}

Then simplify the component code:

 <div className="mb-6 text-[15px] font-bold text-text-muted transition-colors duration-300 px-1">
-  {t("results.found")}{" "}
-  <strong className="text-primary">{filteredData.length}</strong>{" "}
-  {filteredData.length === 1
-    ? t("results.tournaments_one")
-    : t("results.tournaments_many")}
+  {t("results.found")} <strong className="text-primary">{filteredData.length}</strong> {t("results.tournaments", { count: filteredData.length })}
 </div>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/TournamentsPage/TournamentsPage.tsx` around lines 121 -
127, Replace the manual ternary that chooses singular/plural with i18next
pluralization: pass filteredData.length as the count to t (e.g.,
t("results.tournaments", { count: filteredData.length })) in the TournamentsPage
JSX instead of using filteredData.length === 1 ? ... : ...; also update the
translations to provide plural forms for the key "results.tournaments" using the
_one and _other suffixes (e.g., "results.tournaments_one" and
"results.tournaments_other") so i18next can resolve the correct form.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
frontend/src/App.tsx (1)

116-116: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Socket reconnects unnecessarily on language change.

The socket useEffect depends on t (line 116), so changing the app language causes the socket to disconnect and reconnect. This creates unnecessary network overhead and brief service interruption.

🔧 Proposed fix using a ref
 export const App = () => {
   const { t } = useTranslation("common");
+  const tRef = useRef(t);
+
+  useEffect(() => {
+    tRef.current = t;
+  }, [t]);

   // ... (socket setup remains the same until the handlers)

   useEffect(() => {
     let currentSocket: Socket | null = null;

     const unsubscribeAuth = onIdTokenChanged(auth, async (user) => {
       // ... existing code ...
       
       currentSocket.on("connect_error", () => {
         // ... existing code ...
         toast.error(
           <div>
-            <div className="font-bold mb-1">{t("errors.socket")}</div>
+            <div className="font-bold mb-1">{tRef.current("errors.socket")}</div>
             <div className="text-[13px] opacity-90 leading-tight">
               Сповіщення тимчасово не працюють
             </div>
           </div>,
           { toastId: "socket-error" }
         );
       });
       
       // ... rest of socket setup ...
     });

     return () => {
       unsubscribeAuth();
       if (currentSocket) currentSocket.disconnect();
     };
-  }, [t]);
+  }, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` at line 116, The socket useEffect in App.tsx is
incorrectly depending on the i18n translator `t`, causing the socket to
disconnect/reconnect on language changes; update the effect that creates/manages
the socket (the useEffect which currently has dependency array [t]) to remove
`t` from its dependencies and instead use a stable ref or state for any values
that must persist across language changes (e.g., store the socket instance in a
useRef like socketRef and read/write from socketRef inside other effects or
callbacks), ensuring socket creation/cleanup only runs on mount/unmount (empty
deps or relevant stable deps) while keeping language-driven UI updates separate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/package.json`:
- Around line 35-36: Remove the duplicate "lucide-react" entry in package.json
so there is only one dependency for lucide-react (choose the intended version,
e.g., keep "^1.14.0" and delete the "^1.8.0" line), then run your package
manager (npm install / yarn install / pnpm install) to update lockfiles and
ensure the resolved version is consistent; verify that no other files reference
the removed version.

In `@frontend/src/App.tsx`:
- Line 87: The App component currently uses hardcoded Ukrainian strings in the
toast calls (notifications) which bypass i18n; add the missing keys
("errors.socket", "errors.socket_detail", "errors.socket_restored") to your
locale files (e.g., locales/en/common.json and locales/uk/common.json) and
update the toast usages inside the App component to use the i18n translator
(e.g., t('errors.socket') / t('errors.socket_detail') /
t('errors.socket_restored')) instead of the literal Ukrainian text so messages
respect the user's selected language.

---

Duplicate comments:
In `@frontend/src/App.tsx`:
- Line 116: The socket useEffect in App.tsx is incorrectly depending on the i18n
translator `t`, causing the socket to disconnect/reconnect on language changes;
update the effect that creates/manages the socket (the useEffect which currently
has dependency array [t]) to remove `t` from its dependencies and instead use a
stable ref or state for any values that must persist across language changes
(e.g., store the socket instance in a useRef like socketRef and read/write from
socketRef inside other effects or callbacks), ensuring socket creation/cleanup
only runs on mount/unmount (empty deps or relevant stable deps) while keeping
language-driven UI updates separate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f24ca0d-4131-45d1-bbc2-a5c2af6a73cb

📥 Commits

Reviewing files that changed from the base of the PR and between 22e61a6 and 4fe2bec.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (11)
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/components/Header.tsx
  • frontend/src/components/ui/Ticket3D.tsx
  • frontend/src/config/navigation.ts
  • frontend/src/locales/en/common.json
  • frontend/src/locales/en/registration.json
  • frontend/src/locales/uk/common.json
  • frontend/src/locales/uk/registration.json
  • frontend/src/pages/Page404/Page404.tsx
  • frontend/src/routers/Router.tsx

Comment thread frontend/package.json Outdated
Comment thread frontend/src/App.tsx Outdated
Copy link
Copy Markdown
Member

@Fundi1330 Fundi1330 left a comment

Choose a reason for hiding this comment

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

Merge as fast as possible, because we have other work to do

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/components/ui/DateTimePicker.tsx (1)

70-78: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Minute adjustments ignore hour rollover/borrow.

When minutes cross 59→00 or 00→59, the hour is not updated, so resulting time is incorrect (e.g., 10:58 + 5 becomes 10:03 instead of 11:03).

💡 Suggested fix
   const adjustTime = (type: "hours" | "minutes", amount: number) => {
     let nextDate = new Date(dateValue);
     if (type === "hours") {
       const newHours = (nextDate.getHours() + amount + 24) % 24;
       nextDate = setHours(nextDate, newHours);
     } else {
-      const newMinutes = (nextDate.getMinutes() + amount + 60) % 60;
-      nextDate = setMinutes(nextDate, newMinutes);
+      nextDate = new Date(nextDate.getTime() + amount * 60_000);
     }
     updateDateTime(nextDate);
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/DateTimePicker.tsx` around lines 70 - 78, The
adjustTime function currently updates minutes without adjusting hours on
rollover; modify adjustTime (referencing adjustTime, dateValue, setMinutes,
setHours) so when type === "minutes" compute the total minutes (currentMinutes +
amount), derive newMinutes = ((totalMinutes % 60) + 60) % 60 and hourDelta =
Math.floor(totalMinutes / 60) (or -1 for negative wrap), then call
setMinutes(nextDate, newMinutes) and update hours with setHours(nextDate,
(nextDate.getHours() + hourDelta + 24) % 24) to correctly handle both forward
and backward minute rollovers.
♻️ Duplicate comments (4)
frontend/src/locales/en/common.json (1)

54-54: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix aria-label grammar

Line 54 uses "Choice language", which is ungrammatical for an accessibility label. Use "Choose language" (or "Language selection").

✏️ Proposed fix
-    "aria_label": "Choice language"
+    "aria_label": "Choose language"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/locales/en/common.json` at line 54, The aria-label value for the
localization key "aria_label" is ungrammatical; update the string value in
frontend/src/locales/en/common.json for the "aria_label" key from "Choice
language" to a proper accessibility label such as "Choose language" (or
"Language selection") so screen readers convey the intended action; ensure you
only modify the value for the "aria_label" key and keep JSON formatting intact.
frontend/src/pages/Auth/AuthPage.tsx (1)

198-198: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid unsafe casting of validation messages before translation

At Lines 198, 227, and 273, errors.*.message as string can pass non-string/undefined values into t(...). Guard and normalize before translating.

🔧 Proposed fix pattern
- {t(errors.email.message as string)}
+ {t(typeof errors.email.message === "string" ? errors.email.message : "errors.unknown")}

Also applies to: 227-227, 273-273

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Auth/AuthPage.tsx` at line 198, The three places using
unsafe casts (errors.displayName.message as string at lines referenced) should
guard and normalize the message before passing to the i18n translate function
`t`: in the AuthPage component locate the usages of
`errors.displayName.message`, `errors.email.message`, and
`errors.password.message` and replace the cast with a safe extraction such as
checking existence and type (e.g. use a ternary or nullish coalescing to
fallback to an empty string or String(...) conversion) so `t(...)` always
receives a string; ensure you reference the same `t` call sites and preserve
existing behavior for empty/undefined messages by supplying a sensible default.
frontend/package.json (1)

33-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Upgrade or remove vulnerable i18next-http-backend version

Line 33 still pins i18next-http-backend to ^3.0.4, which was previously flagged for a path traversal/URL injection advisory. Please bump to a patched version (>=3.0.5) or remove it if unused.

🔧 Proposed minimal fix
-    "i18next-http-backend": "^3.0.4",
+    "i18next-http-backend": "^3.0.5",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/package.json` at line 33, Update the vulnerable dependency
declaration for i18next-http-backend in package.json: either bump the version
string for "i18next-http-backend" from "^3.0.4" to a patched release ">=3.0.5"
(e.g. "^3.0.5") or remove the dependency entry if it's not used; after changing
package.json, run your package manager to update the lockfile (npm/yarn/pnpm
install) and run the frontend tests/build to verify nothing breaks. Ensure any
imports/usages of i18next-http-backend in the codebase are updated/removed
accordingly if you choose to remove the package.
frontend/src/App.tsx (1)

55-137: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Socket effect still depends on translation function.

The socket useEffect continues to depend on t (line 137), causing the socket to disconnect and reconnect whenever the user switches languages. This was previously flagged and marked as addressed, but the issue persists.

🔄 Recommended fix using a ref to decouple lifecycle
 export const App = () => {
   const { t } = useTranslation("common");
+  const tRef = useRef(t);
   const dispatch = useDispatch();
   const [socket, setSocket] = useState<Socket | null>(null);
   const [toastTheme, setToastTheme] = useState<"colored" | "dark">("colored");

   const lastErrorTime = useRef(0);
   const wasError = useRef(false);

+  useEffect(() => {
+    tRef.current = t;
+  }, [t]);

   // ... theme effect ...

   useEffect(() => {
     let currentSocket: Socket | null = null;

     const unsubscribeAuth = onIdTokenChanged(auth, async (user) => {
       // ... existing logic ...

       currentSocket.on("connect_error", () => {
         const now = Date.now();
         if (
           now - lastErrorTime.current > COOLDOWN_TIME ||
           !wasError.current
         ) {
           toast.error(
             <div>
-              <div className="font-bold mb-1">{t("errors.socket")}</div>
+              <div className="font-bold mb-1">{tRef.current("errors.socket")}</div>
               <div className="text-[13px] opacity-90 leading-tight">
-                {t("errors.socket_desc")}{" "}
+                {tRef.current("errors.socket_desc")}{" "}
               </div>
             </div>,
             { toastId: "socket-error" },
           );
           lastErrorTime.current = now;
           wasError.current = true;
         }
       });

       currentSocket.on("connect", () => {
         toast.dismiss("socket-error");
         if (wasError.current) {
-          toast.success(t("success.socket_restored"), {
+          toast.success(tRef.current("success.socket_restored"), {
             toastId: "socket-success",
           });
           wasError.current = false;
           lastErrorTime.current = 0;
         }
       });

       setSocket(currentSocket);
       // ... rest of logic ...
     });

     return () => {
       unsubscribeAuth();
       if (currentSocket) currentSocket.disconnect();
     };
-  }, [t, dispatch]);
+  }, [dispatch]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` around lines 55 - 137, The effect that manages the
socket connection (the useEffect containing onIdTokenChanged, currentSocket,
setSocket, and handlers using lastErrorTime.current and wasError.current)
wrongly lists the translation function t in its dependency array, causing
reconnects when language changes; remove t from the dependency list and instead
access translations only inside handlers via a stable ref or by calling t at the
point you show notifications through a separate helper (e.g., create a
toastMessagesRef or getToastMessage function outside the effect and reference
that), ensuring the effect depends only on stable values (dispatch, auth) and
that socket logic (currentSocket, connect/connect_error handlers,
unsubscribeAuth, setSocket) is not re-run on language switch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/App.tsx`:
- Around line 112-113: Remove the empty JSX expression block `{ }` found in the
App component's render/return JSX (the stray empty block around lines 112-113);
simply delete that empty expression so the JSX has no redundant empty JS block
and the component's returned JSX remains unchanged otherwise.
- Line 125: Replace the hardcoded Ukrainian console message in the App
component's error handler with an i18n lookup: add "errors.profile_sync" to your
locale files (frontend/src/locales/en/common.json and
frontend/src/locales/uk/common.json) and change the console.error call in App
(where "Помилка синхронізації профілю" is used) to use the i18n translator
(e.g., t('errors.profile_sync')) obtained from your i18n hook/instance
(useTranslation or equivalent) so the logged message follows the user's language
setting and still includes the original error object.

In `@frontend/src/components/ui/DateTimePicker.tsx`:
- Around line 135-136: The code in DateTimePicker.tsx hardcodes Ukrainian
strings and a fixed 'uk' locale (e.g., the "Виберіть дату" string and uses
format(dateValue, "dd.MM.yyyy", { locale: uk })) which prevents runtime language
switching; update the component (DateTimePicker) to use the app's i18n
translation function (e.g., t('selectDate')) instead of the literal "Виберіть
дату" wherever value is falsy, and pass a dynamic locale object to
date-fns/format (derive the locale from the current app language/locale prop or
context) so calls like format(dateValue, "dd.MM.yyyy", { locale: dynamicLocale
}) replace any hardcoded uk usage (also fix the other occurrences around lines
161, 181-187, 195 that mirror this pattern).
- Around line 135-136: The DateTimePicker component is calling format(dateValue,
...) without ensuring dateValue is a valid Date, causing runtime throws when
parseISO(value) is invalid; update the component (references: DateTimePicker,
parseISO, dateValue, value, and every format(...) call at the mentioned render
locations) to first parse the value, check isValid(parsedDate) (from date-fns)
and only call format when valid, otherwise render a safe fallback string (e.g.,
the placeholder "Виберіть дату" or the raw value) so format() is never invoked
with an invalid date.

In `@frontend/src/i18n/config.ts`:
- Around line 67-78: The i18n namespace list `ns` is missing the "profile"
namespace that is included in the bundled resources; update the `ns` array (in
config.ts where `ns: [...]` is declared) to include "profile" alongside
"common", "auth", "registration", etc., so the `profile` resource is initialized
correctly.

In `@frontend/src/locales/uk/common.json`:
- Around line 40-44: The Ukrainian errors.404 object is missing the
errors.404.back translation key present in English; update the uk common
translations by adding the errors.404.back entry inside the existing "404"
object (the same place as "title", "description", "go_home") with an appropriate
Ukrainian string (e.g., "Повернутися назад") so the 404 page doesn't render an
untranslated key.

In `@frontend/src/pages/Auth/AuthPage.tsx`:
- Around line 263-269: The password visibility toggle button in AuthPage.tsx
(the button using showPassword, setShowPassword and icons Eye/EyeOff) lacks an
accessible name; add an aria-label that updates based on showPassword (e.g.,
"Hide password" when showPassword is true, "Show password" when false) so screen
readers can describe the action, and optionally include
aria-pressed={showPassword} to convey state.

In `@frontend/src/pages/Profile/EditProfileModal.tsx`:
- Around line 19-24: EditProfileModalProps currently types currentUser as
any—replace that with a concrete user interface that includes the fields used by
the component (displayName, full_name, telegram, github, discord) or import and
reuse the existing UserData interface from Profile.tsx; update the
EditProfileModalProps definition to reference this interface (e.g., currentUser:
UserData) and adjust any local usages if necessary to satisfy TypeScript.

In `@frontend/src/pages/Profile/Profile.tsx`:
- Around line 72-77: The map callback in userTournaments is shadowing the
translation function t from useTranslation("profile"); rename the map parameter
(e.g., from t to tournament or createdTournament) and update its uses inside the
callback (t.title || t.name -> tournament.title || tournament.name) so that
calls to t("tournament_unnamed") and t("no_tournaments") refer to the
translation function, not the mapped object.

---

Outside diff comments:
In `@frontend/src/components/ui/DateTimePicker.tsx`:
- Around line 70-78: The adjustTime function currently updates minutes without
adjusting hours on rollover; modify adjustTime (referencing adjustTime,
dateValue, setMinutes, setHours) so when type === "minutes" compute the total
minutes (currentMinutes + amount), derive newMinutes = ((totalMinutes % 60) +
60) % 60 and hourDelta = Math.floor(totalMinutes / 60) (or -1 for negative
wrap), then call setMinutes(nextDate, newMinutes) and update hours with
setHours(nextDate, (nextDate.getHours() + hourDelta + 24) % 24) to correctly
handle both forward and backward minute rollovers.

---

Duplicate comments:
In `@frontend/package.json`:
- Line 33: Update the vulnerable dependency declaration for i18next-http-backend
in package.json: either bump the version string for "i18next-http-backend" from
"^3.0.4" to a patched release ">=3.0.5" (e.g. "^3.0.5") or remove the dependency
entry if it's not used; after changing package.json, run your package manager to
update the lockfile (npm/yarn/pnpm install) and run the frontend tests/build to
verify nothing breaks. Ensure any imports/usages of i18next-http-backend in the
codebase are updated/removed accordingly if you choose to remove the package.

In `@frontend/src/App.tsx`:
- Around line 55-137: The effect that manages the socket connection (the
useEffect containing onIdTokenChanged, currentSocket, setSocket, and handlers
using lastErrorTime.current and wasError.current) wrongly lists the translation
function t in its dependency array, causing reconnects when language changes;
remove t from the dependency list and instead access translations only inside
handlers via a stable ref or by calling t at the point you show notifications
through a separate helper (e.g., create a toastMessagesRef or getToastMessage
function outside the effect and reference that), ensuring the effect depends
only on stable values (dispatch, auth) and that socket logic (currentSocket,
connect/connect_error handlers, unsubscribeAuth, setSocket) is not re-run on
language switch.

In `@frontend/src/locales/en/common.json`:
- Line 54: The aria-label value for the localization key "aria_label" is
ungrammatical; update the string value in frontend/src/locales/en/common.json
for the "aria_label" key from "Choice language" to a proper accessibility label
such as "Choose language" (or "Language selection") so screen readers convey the
intended action; ensure you only modify the value for the "aria_label" key and
keep JSON formatting intact.

In `@frontend/src/pages/Auth/AuthPage.tsx`:
- Line 198: The three places using unsafe casts (errors.displayName.message as
string at lines referenced) should guard and normalize the message before
passing to the i18n translate function `t`: in the AuthPage component locate the
usages of `errors.displayName.message`, `errors.email.message`, and
`errors.password.message` and replace the cast with a safe extraction such as
checking existence and type (e.g. use a ternary or nullish coalescing to
fallback to an empty string or String(...) conversion) so `t(...)` always
receives a string; ensure you reference the same `t` call sites and preserve
existing behavior for empty/undefined messages by supplying a sensible default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 88ca65f3-e26e-4eb5-8e62-92e6fc4ac4cd

📥 Commits

Reviewing files that changed from the base of the PR and between 4fe2bec and 49476dc.

⛔ Files ignored due to path filters (1)
  • frontend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (15)
  • frontend/package.json
  • frontend/src/App.tsx
  • frontend/src/components/ui/DateTimePicker.tsx
  • frontend/src/firebase.ts
  • frontend/src/i18n/config.ts
  • frontend/src/locales/en/common.json
  • frontend/src/locales/en/profile.json
  • frontend/src/locales/uk/common.json
  • frontend/src/locales/uk/profile.json
  • frontend/src/pages/Auth/AuthPage.tsx
  • frontend/src/pages/Profile/EditProfileModal.test.tsx
  • frontend/src/pages/Profile/EditProfileModal.tsx
  • frontend/src/pages/Profile/Profile.test.tsx
  • frontend/src/pages/Profile/Profile.tsx
  • frontend/src/slices/user.ts
💤 Files with no reviewable changes (1)
  • frontend/src/firebase.ts

Comment thread frontend/src/App.tsx
Comment on lines +112 to +113
{
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Remove empty code block.

Lines 112-113 contain an empty block { } that serves no purpose and should be removed.

🧹 Cleanup
           toast.success(t("success.socket_restored"), {
             toastId: "socket-success",
           });
-          {
-          }
           wasError.current = false;
           lastErrorTime.current = 0;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{
}
toast.success(t("success.socket_restored"), {
toastId: "socket-success",
});
wasError.current = false;
lastErrorTime.current = 0;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` around lines 112 - 113, Remove the empty JSX expression
block `{ }` found in the App component's render/return JSX (the stray empty
block around lines 112-113); simply delete that empty expression so the JSX has
no redundant empty JS block and the component's returned JSX remains unchanged
otherwise.

Comment thread frontend/src/App.tsx
window.location.replace("/");
}
} catch (error) {
console.error("Помилка синхронізації профілю:", error);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace hardcoded Ukrainian error message with i18n key.

The console error at line 125 contains a hardcoded Ukrainian string that bypasses the i18n system and will always display in Ukrainian regardless of the user's language preference.

🌐 Proposed fix

First, add the missing key to your locale files:

frontend/src/locales/en/common.json:

{
  "errors": {
    "profile_sync": "Profile synchronization error"
  }
}

frontend/src/locales/uk/common.json:

{
  "errors": {
    "profile_sync": "Помилка синхронізації профілю"
  }
}

Then update the error handler:

         } catch (error) {
-          console.error("Помилка синхронізації профілю:", error);
+          console.error(t("errors.profile_sync"), error);
           dispatch(clearUser());
         }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.error("Помилка синхронізації профілю:", error);
console.error(t("errors.profile_sync"), error);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/App.tsx` at line 125, Replace the hardcoded Ukrainian console
message in the App component's error handler with an i18n lookup: add
"errors.profile_sync" to your locale files (frontend/src/locales/en/common.json
and frontend/src/locales/uk/common.json) and change the console.error call in
App (where "Помилка синхронізації профілю" is used) to use the i18n translator
(e.g., t('errors.profile_sync')) obtained from your i18n hook/instance
(useTranslation or equivalent) so the logged message follows the user's language
setting and still includes the original error object.

Comment on lines +135 to 136
{value ? format(dateValue, "dd.MM.yyyy") : "Виберіть дату"}
</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Hardcoded Ukrainian text/locale blocks runtime language switching.

These labels and the forced uk locale make the picker non-translatable in English mode.

💡 Suggested fix
- {value ? format(dateValue, "dd.MM.yyyy") : "Виберіть дату"}
+ {value ? format(dateValue, "dd.MM.yyyy") : t("common.selectDate")}

- {format(currentMonth, "LLLL yyyy", { locale: uk })}
+ {format(currentMonth, "LLLL yyyy", { locale: dateFnsLocale })}

- {["Пн", "Вв", "Ср", "Чт", "Пт", "Сб", "Нд"].map((d) => (
+ {weekDayLabels.map((d) => (

- Час
+ {t("common.time")}

Also applies to: 161-161, 181-187, 195-195

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/DateTimePicker.tsx` around lines 135 - 136, The
code in DateTimePicker.tsx hardcodes Ukrainian strings and a fixed 'uk' locale
(e.g., the "Виберіть дату" string and uses format(dateValue, "dd.MM.yyyy", {
locale: uk })) which prevents runtime language switching; update the component
(DateTimePicker) to use the app's i18n translation function (e.g.,
t('selectDate')) instead of the literal "Виберіть дату" wherever value is falsy,
and pass a dynamic locale object to date-fns/format (derive the locale from the
current app language/locale prop or context) so calls like format(dateValue,
"dd.MM.yyyy", { locale: dynamicLocale }) replace any hardcoded uk usage (also
fix the other occurrences around lines 161, 181-187, 195 that mirror this
pattern).

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cd frontend/src/components/ui && cat -n DateTimePicker.tsx | sed -n '1,150p'

Repository: ugalaxy-team/Project

Length of output: 5582


🌐 Web query:

date-fns format() invalid date throws error

💡 Result:

Yes, the date-fns format function throws a RangeError: "Invalid time value" when passed an invalid date.[1] This occurs because format internally calls toDate on the provided argument to normalize it to a Date instance.[2][1] If the resulting date is invalid (i.e., isValid returns false), it throws the error.[1] Common causes include: - Passing a string directly instead of a Date object or timestamp. date-fns v2+ does not automatically parse strings (unlike v1); use new Date or parseISO first.[3][4][5] - new Date receiving an invalid string or value, producing an Invalid Date.[6] - Invalid numerical timestamps.[2] Example of error: import { format } from 'date-fns'; format('invalid', 'yyyy-MM-dd'); // RangeError: Invalid time value Fixes: const date = new Date('2023-01-01'); if (isValid(date)) { format(date, 'yyyy-MM-dd'); // Works } For ISO strings, use parseISO: import { parseISO } from 'date-fns'; format(parseISO('2023-01-01'), 'yyyy-MM-dd'); This behavior is consistent in current versions (v3+).[1][2]

Citations:


🏁 Script executed:

cd frontend/src/components/ui && grep -n "dateValue" DateTimePicker.tsx

Repository: ugalaxy-team/Project

Length of output: 714


🏁 Script executed:

cd frontend/src/components/ui && wc -l DateTimePicker.tsx && cat -n DateTimePicker.tsx | sed -n '200,250p'

Repository: ugalaxy-team/Project

Length of output: 2681


Guard all format() calls against invalid value input.

If value is malformed, parseISO(value) yields an invalid date and format() throws at render time. This affects multiple render locations (lines 135, 140, 208, 230).

Suggested fix
 import {
   format,
+  isValid,
   addMonths,
   subMonths,
   startOfMonth,
   endOfMonth,
   startOfWeek,
   endOfWeek,
   isSameMonth,
   isSameDay,
   addDays,
   setHours,
   setMinutes,
   parseISO,
 } from "date-fns";
 
 const DateTimePicker: React.FC<DateTimePickerProps> = ({
   value,
   onChange,
   label,
 }) => {
-  const dateValue = value ? parseISO(value) : new Date();
+  const parsed = value ? parseISO(value) : new Date();
+  const dateValue = isValid(parsed) ? parsed : new Date();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/components/ui/DateTimePicker.tsx` around lines 135 - 136, The
DateTimePicker component is calling format(dateValue, ...) without ensuring
dateValue is a valid Date, causing runtime throws when parseISO(value) is
invalid; update the component (references: DateTimePicker, parseISO, dateValue,
value, and every format(...) call at the mentioned render locations) to first
parse the value, check isValid(parsedDate) (from date-fns) and only call format
when valid, otherwise render a safe fallback string (e.g., the placeholder
"Виберіть дату" or the raw value) so format() is never invoked with an invalid
date.

Comment on lines +67 to +78
ns: [
"common",
"auth",
"registration",
"home",
"about",
"faq",
"support",
"contact",
"tournaments",
"rules",
],
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "config.ts" | grep -i i18n

Repository: ugalaxy-team/Project

Length of output: 94


🏁 Script executed:

cat -n frontend/src/i18n/config.ts | head -90

Repository: ugalaxy-team/Project

Length of output: 2941


Add profile to the namespace list

The profile namespace is bundled in resources (lines 43 and 56) but missing from the ns array (lines 67-78). Add it to ensure proper namespace initialization.

    ns: [
      "common",
      "auth",
      "registration",
      "home",
      "about",
      "faq",
      "support",
      "contact",
      "tournaments",
      "rules",
+     "profile",
    ],
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ns: [
"common",
"auth",
"registration",
"home",
"about",
"faq",
"support",
"contact",
"tournaments",
"rules",
],
ns: [
"common",
"auth",
"registration",
"home",
"about",
"faq",
"support",
"contact",
"tournaments",
"rules",
"profile",
],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/i18n/config.ts` around lines 67 - 78, The i18n namespace list
`ns` is missing the "profile" namespace that is included in the bundled
resources; update the `ns` array (in config.ts where `ns: [...]` is declared) to
include "profile" alongside "common", "auth", "registration", etc., so the
`profile` resource is initialized correctly.

Comment on lines +40 to +44
"404": {
"title": "Упс, пусто!",
"description": "Схоже, цієї сторінки не існує. Можливо, вона переїхала або ви відлетіли занадто далеко в космос.",
"go_home": "На головну"
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add missing errors.404.back translation key

en/common.json includes errors.404.back, but Ukrainian errors.404 does not. This can cause untranslated key output on the 404 page for uk.

🌐 Proposed fix
   "errors": {
     "404": {
       "title": "Упс, пусто!",
       "description": "Схоже, цієї сторінки не існує. Можливо, вона переїхала або ви відлетіли занадто далеко в космос.",
+      "back": "Назад",
       "go_home": "На головну"
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"404": {
"title": "Упс, пусто!",
"description": "Схоже, цієї сторінки не існує. Можливо, вона переїхала або ви відлетіли занадто далеко в космос.",
"go_home": "На головну"
},
"404": {
"title": "Упс, пусто!",
"description": "Схоже, цієї сторінки не існує. Можливо, вона переїхала або ви відлетіли занадто далеко в космос.",
"back": "Назад",
"go_home": "На головну"
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/locales/uk/common.json` around lines 40 - 44, The Ukrainian
errors.404 object is missing the errors.404.back translation key present in
English; update the uk common translations by adding the errors.404.back entry
inside the existing "404" object (the same place as "title", "description",
"go_home") with an appropriate Ukrainian string (e.g., "Повернутися назад") so
the 404 page doesn't render an untranslated key.

Comment on lines +263 to +269
<button
type="button"
className="absolute right-4 text-text-muted hover:text-text-main transition-colors p-1"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add accessible name to password visibility toggle

The icon-only button at Line 263 has no aria-label, so screen-reader users can’t identify its action.

♿ Proposed fix
             <button
               type="button"
               className="absolute right-4 text-text-muted hover:text-text-main transition-colors p-1"
               onClick={() => setShowPassword(!showPassword)}
+              aria-label={showPassword ? t("fields.password.hide") : t("fields.password.show")}
+              aria-pressed={showPassword}
             >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
type="button"
className="absolute right-4 text-text-muted hover:text-text-main transition-colors p-1"
onClick={() => setShowPassword(!showPassword)}
>
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
<button
type="button"
className="absolute right-4 text-text-muted hover:text-text-main transition-colors p-1"
onClick={() => setShowPassword(!showPassword)}
aria-label={showPassword ? t("fields.password.hide") : t("fields.password.show")}
aria-pressed={showPassword}
>
{showPassword ? <EyeOff size={20} /> : <Eye size={20} />}
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Auth/AuthPage.tsx` around lines 263 - 269, The password
visibility toggle button in AuthPage.tsx (the button using showPassword,
setShowPassword and icons Eye/EyeOff) lacks an accessible name; add an
aria-label that updates based on showPassword (e.g., "Hide password" when
showPassword is true, "Show password" when false) so screen readers can describe
the action, and optionally include aria-pressed={showPassword} to convey state.

Comment on lines +19 to 24
// Додали currentUser, оскільки ми передаємо його з Profile.tsx
interface EditProfileModalProps {
isOpen: boolean;
onClose: () => void;
currentUser: any;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Replace any type with proper interface for currentUser prop.

The currentUser prop is typed as any, which defeats TypeScript's type safety. Since the component uses fields like displayName, full_name, telegram, github, and discord, define a proper interface or reuse the UserData interface from the parent component.

🔧 Suggested fix
+interface CurrentUserData {
+  uid: string;
+  displayName?: string;
+  full_name?: string;
+  telegram?: string;
+  github?: string;
+  discord?: string;
+}
+
 interface EditProfileModalProps {
   isOpen: boolean;
   onClose: () => void;
-  currentUser: any;
+  currentUser: CurrentUserData;
 }

Alternatively, if UserData from Profile.tsx can be shared, import and reuse it:

+import type { UserData } from './Profile';
+
 interface EditProfileModalProps {
   isOpen: boolean;
   onClose: () => void;
-  currentUser: any;
+  currentUser: UserData;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Profile/EditProfileModal.tsx` around lines 19 - 24,
EditProfileModalProps currently types currentUser as any—replace that with a
concrete user interface that includes the fields used by the component
(displayName, full_name, telegram, github, discord) or import and reuse the
existing UserData interface from Profile.tsx; update the EditProfileModalProps
definition to reference this interface (e.g., currentUser: UserData) and adjust
any local usages if necessary to satisfy TypeScript.

Comment on lines +72 to +77
const userTournaments =
user.created_tournaments && user.created_tournaments.length > 0
? user.created_tournaments.map(
(t) => t.title || t.name || t("tournament_unnamed"),
)
: [t("no_tournaments")];
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix variable shadowing bug in tournament mapping.

Line 75 contains a critical bug: the map callback parameter t shadows the translation function t from useTranslation("profile"). When the code tries to call t("tournament_unnamed"), it's actually calling the tournament object as a function, which will cause a runtime error.

🐛 Fix the variable name collision
   const userTournaments =
     user.created_tournaments && user.created_tournaments.length > 0
       ? user.created_tournaments.map(
-          (t) => t.title || t.name || t("tournament_unnamed"),
+          (tournament) => tournament.title || tournament.name || t("tournament_unnamed"),
         )
       : [t("no_tournaments")];
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const userTournaments =
user.created_tournaments && user.created_tournaments.length > 0
? user.created_tournaments.map(
(t) => t.title || t.name || t("tournament_unnamed"),
)
: [t("no_tournaments")];
const userTournaments =
user.created_tournaments && user.created_tournaments.length > 0
? user.created_tournaments.map(
(tournament) => tournament.title || tournament.name || t("tournament_unnamed"),
)
: [t("no_tournaments")];
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/pages/Profile/Profile.tsx` around lines 72 - 77, The map
callback in userTournaments is shadowing the translation function t from
useTranslation("profile"); rename the map parameter (e.g., from t to tournament
or createdTournament) and update its uses inside the callback (t.title || t.name
-> tournament.title || tournament.name) so that calls to t("tournament_unnamed")
and t("no_tournaments") refer to the translation function, not the mapped
object.

@izachoc izachoc merged commit c9b114a into dev May 13, 2026
2 of 3 checks passed
@izachoc izachoc deleted the feature/frontend-redesign branch May 13, 2026 20:03
@coderabbitai coderabbitai Bot mentioned this pull request May 15, 2026
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.

3 participants