AI Review Center
Commit Review Workspace ?
Select a commit on the left to inspect quality, security, business value, findings, and suggested code changes.
Project AI Score
?
73%
Quality Avg
?
74%
Security Avg
?
74%
Reviews
?
130
Review Result ?
solutionbowl/crm-dashbaord · 3b0752ad
Introduces a PermissionSyncIndicator React component that visually displays when user permissions are being refreshed automatically as per a configured interval. Adds configuration for permission refresh in auth.config and minor enhancements in useAuth hook for permission refreshing. The implementation improves user experience by making permission syncing status transparent and leverages existing app authentication mechanisms. Code is clean and well-commented. However, the commit message is uninformative, and the injecting of CSS via JavaScript can be improved for maintainability. Minor risk of bug if interval controls are not managed carefully.
Quality
?
85%
Security
?
70%
Business Value
?
75%
Maintainability
?
85%
Issues & Suggested Fixes
?
1AI detects the issue
2Shows file, line, or evidence
3Suggests the fix to apply
Non Descriptive Message
Where
commit message
Issue / Evidence
non-descriptive message
Suggested Fix
provide a detailed commit message summarizing key changes and motivation
Injecting Css Directly Into Document.head
Where
src/components/PermissionSyncIndicator.tsx:113
Issue / Evidence
injecting CSS directly into document.head
Suggested Fix
move CSS animations to dedicated CSS or styled-component file
No Error Handling On Setinterval Callbacks
Where
src/components/PermissionSyncIndicator.tsx:24
Issue / Evidence
no error handling on setInterval callbacks
Suggested Fix
consider error boundaries or try-catch to avoid silent failures
Settimeout Inside Setinterval Can Cause Ov...
Where
src/components/PermissionSyncIndicator.tsx:26
Issue / Evidence
setTimeout inside setInterval can cause overlapping calls if interval is too short
Suggested Fix
ensure refresh interval is longer than timeout or manage state accordingly
Security Issue
Where
src/hooks/useAuth.ts:29
Issue / Evidence
re-checking authentication inside useEffect
Suggested Fix
consider memoizing or passing isAuthenticated as dependency to prevent unnecessary re-renders
Code Change Preview · src/components/header/header.tsx
?
Removed / Before Commit
- diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx - index 7f5a0c4..c372a75 100644 - import React, { Fragment, useEffect, useRef, useState } from 'react' - import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap' - {/*import { useDispatch, useSelector } from 'react-redux' - import SimpleBar from 'simplebar-react' - import Switcher from '../switcher/switcher'*/} - import { data$, getState, setState } from '../services/switcherServices' - {/*import { MENUITEMS } from '../sidebar/nav'*/} - import { Link, useNavigate } from 'react-router-dom' - {/*import SpkListgroup from '../common/ui/@spk-reusable-components/reusable-uiElements/spk-listgroup' - import SpkBadge from '../common/ui/@spk-reusable-components/reusable-uiElements/spk-badge' - import SpkButton from '../common/ui/@spk-reusable-components/reusable-uiElements/spk-buttons' - import SpkTooltips from '../common/ui/@spk-reusable-components/reusable-uiElements/spk-tooltips' - import { removeCart } from '../common/ui/redux/actions'*/} - import desktoplogo from "../../assets/images/brand-logos/desktop-logo.png"; - import desktopdark from "../../assets/images/brand-logos/desktop-dark.png"; - import togglelogo from "../../assets/images/brand-logos/toggle-logo.png";
Added / After Commit
+ diff --git a/src/components/header/header.tsx b/src/components/header/header.tsx + index 7f5a0c4..c372a75 100644 + import React, { Fragment, useEffect, useRef, useState } from 'react' + import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap' + + import { data$, getState, setState } from '../services/switcherServices' + {/*import { MENUITEMS } from '../sidebar/nav'*/} + import { Link, useNavigate } from 'react-router-dom' + + import desktoplogo from "../../assets/images/brand-logos/desktop-logo.png"; + import desktopdark from "../../assets/images/brand-logos/desktop-dark.png"; + import togglelogo from "../../assets/images/brand-logos/toggle-logo.png"; + const handleShow1 = () => {}; + + //full screen + + {/*Menu close function */} + function menuClose() {
Removed / Before Commit
- diff --git a/src/components/PermissionSyncIndicator.tsx b/src/components/PermissionSyncIndicator.tsx - new file mode 100644 - index 0000000..b43c309
Added / After Commit
+ diff --git a/src/components/PermissionSyncIndicator.tsx b/src/components/PermissionSyncIndicator.tsx + new file mode 100644 + index 0000000..b43c309 + import React, { useState, useEffect } from 'react'; + import { useAuth } from '../hooks/useAuth'; + import authConfig from '../config/auth.config'; + + /** + * Permission Sync Indicator Component + * + * Shows a visual indicator when permissions are being refreshed. + * Optional component - can be added to header or anywhere in the app. + * + * Usage: + * <PermissionSyncIndicator /> + */ + const PermissionSyncIndicator: React.FC = () => { + const { isAuthenticated } = useAuth();
Removed / Before Commit
- diff --git a/src/config/auth.config.ts b/src/config/auth.config.ts - new file mode 100644 - index 0000000..4046094
Added / After Commit
+ diff --git a/src/config/auth.config.ts b/src/config/auth.config.ts + new file mode 100644 + index 0000000..4046094 + /** + * Authentication Configuration + * + * Configure authentication-related settings here + */ + + export const authConfig = { + /** + * Permission Refresh Interval (in milliseconds) + * + * How often to automatically check for permission updates for logged-in users. + * This ensures that if an admin changes a user's permissions, the user will + * see the updated permissions without having to log out and log back in. + * + * Default: 30000 (30 seconds)
Removed / Before Commit
- diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts - index 6c2849d..ba11fcf 100644 - import { useState, useEffect, useMemo } from 'react'; - import authService from '../services/api/authService'; - import { UserRole } from '../services/api/types'; - - interface AuthUser { - id: string; - export const useAuth = () => { - const [user, setUser] = useState<AuthUser | null>(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const currentUser = authService.getCurrentUser(); - setLoading(false); - }, []); - - Permissions auto-refreshed successfully");
Added / After Commit
+ diff --git a/src/hooks/useAuth.ts b/src/hooks/useAuth.ts + index 6c2849d..ba11fcf 100644 + import { useState, useEffect, useMemo, useRef } from 'react'; + import authService from '../services/api/authService'; + import { UserRole } from '../services/api/types'; + import authConfig from '../config/auth.config'; + + interface AuthUser { + id: string; + export const useAuth = () => { + const [user, setUser] = useState<AuthUser | null>(null); + const [loading, setLoading] = useState(true); + const refreshIntervalRef = useRef<NodeJS.Timeout | null>(null); + + useEffect(() => { + const currentUser = authService.getCurrentUser(); + setLoading(false); + }, []);
Removed / Before Commit
- diff --git a/src/modules/horse/hooks/useRaceOddsSocket.ts b/src/modules/horse/hooks/useRaceOddsSocket.ts - index 0f86469..4def462 100644 - - return () => { - console.log("Cleaning up socket connection for race:", raceId); - socket.off("raceOddsAdmin", handleOddsUpdate); - socket.off("raceOddsTenant", handleOddsUpdate); - socket.off("raceExposureAdmin", handleExposureUpdate);
Added / After Commit
+ diff --git a/src/modules/horse/hooks/useRaceOddsSocket.ts b/src/modules/horse/hooks/useRaceOddsSocket.ts + index 0f86469..4def462 100644 + + return () => { + console.log("Cleaning up socket connection for race:", raceId); + + socket.off("raceOddsAdmin", handleOddsUpdate); + socket.off("raceOddsTenant", handleOddsUpdate); + socket.off("raceExposureAdmin", handleExposureUpdate);
Removed / Before Commit
- diff --git a/src/modules/horse/layout/HorseSidebar.tsx b/src/modules/horse/layout/HorseSidebar.tsx - index 5116463..f5fa940 100644 - import { Link, useLocation } from "react-router-dom"; - import SimpleBar from "simplebar-react"; - import { useAuth } from "../../../hooks/useAuth"; - - /** - * Horse Racing Module Sidebar - return children.some(child => location.pathname.startsWith(child.path)); - }; - - // Check permissions - const hasPermission = (permission: string) => { - if (!permissions || permissions.length === 0) return true; // Full access for main PLATFORM_ADMIN - return items; - }, [permissions]); - - // Add slide menu functionality
Added / After Commit
+ diff --git a/src/modules/horse/layout/HorseSidebar.tsx b/src/modules/horse/layout/HorseSidebar.tsx + index 5116463..f5fa940 100644 + import { Link, useLocation } from "react-router-dom"; + import SimpleBar from "simplebar-react"; + import { useAuth } from "../../../hooks/useAuth"; + import { getState, setState } from "../../../components/services/switcherServices"; + + /** + * Horse Racing Module Sidebar + return children.some(child => location.pathname.startsWith(child.path)); + }; + + const closeSidebarOnMobile = () => { + if (window.innerWidth <= 992) { + setState({ toggled: "close" }); + } + }; +
Removed / Before Commit
- diff --git a/src/modules/horse/pages/Race/AdminRacepage.tsx b/src/modules/horse/pages/Race/AdminRacepage.tsx - index 130d8d7..3b854e1 100644 - normalizedName.includes("PLACE") - ); - }; - const isFancyMarketType = (marketType?: string, marketName?: string) => { - const normalizedType = normalizeMarketType(marketType); - const normalizedName = normalizeMarketType(marketName); - - const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()); - const [globalStates, setGlobalStates] = useState<Record<string, GlobalState>>({}); - - useEffect(() => { - if (selectedRaceId) fetchOdds(selectedRaceId); - setGlobalStates({}); - setMarketDrafts({}); - setRunnerDrafts({}); - }
Added / After Commit
+ diff --git a/src/modules/horse/pages/Race/AdminRacepage.tsx b/src/modules/horse/pages/Race/AdminRacepage.tsx + index 130d8d7..3b854e1 100644 + normalizedName.includes("PLACE") + ); + }; + const isPlaceMarketType = (marketType?: string, marketName?: string) => { + const normalizedType = normalizeMarketType(marketType); + const normalizedName = normalizeMarketType(marketName); + + return ( + !isWinnerMarketType(marketType, marketName) && + (normalizedType.includes("PLACE") || normalizedName.includes("PLACE")) + ); + }; + const isFancyMarketType = (marketType?: string, marketName?: string) => { + const normalizedType = normalizeMarketType(marketType); + const normalizedName = normalizeMarketType(marketName); +
Removed / Before Commit
- diff --git a/src/modules/horse/pages/Race/AllRaces.tsx b/src/modules/horse/pages/Race/AllRaces.tsx - index 6abbe88..24b8ea1 100644 - const fetchRaces = async () => { - try { - setLoading(true); - - const response = await raceService.getAllRaces( - currentPage, - perPage, - searchTerm - ); - - if (response.success) { - setRaces(response.data); - setTotalRows(response.pagination?.total || response.data.length);
Added / After Commit
+ diff --git a/src/modules/horse/pages/Race/AllRaces.tsx b/src/modules/horse/pages/Race/AllRaces.tsx + index 6abbe88..24b8ea1 100644 + const fetchRaces = async () => { + try { + setLoading(true); + const response = await raceService.getAllRaces( + currentPage, + perPage, + searchTerm + ); + if (response.success) { + setRaces(response.data); + setTotalRows(response.pagination?.total || response.data.length);
Removed / Before Commit
- diff --git a/src/modules/horse/pages/Race/RaceDetail.tsx b/src/modules/horse/pages/Race/RaceDetail.tsx - index b20c604..09b75d6 100644 - distance: string; - start_time: string; - status: boolean; - event_id: { - _id: string; - event_name: string; - const [punchersLoading, setPunchersLoading] = useState(false); - const [selectedPuncherId, setSelectedPuncherId] = useState(""); - const [assigning, setAssigning] = useState(false); - - - - const getPuncherDisplayName = (race: RaceDetailData["race"]) => { - } - }; -
Added / After Commit
+ diff --git a/src/modules/horse/pages/Race/RaceDetail.tsx b/src/modules/horse/pages/Race/RaceDetail.tsx + index b20c604..09b75d6 100644 + distance: string; + start_time: string; + status: boolean; + insurance_bet?: boolean + event_id: { + _id: string; + event_name: string; + const [punchersLoading, setPunchersLoading] = useState(false); + const [selectedPuncherId, setSelectedPuncherId] = useState(""); + const [assigning, setAssigning] = useState(false); + const [insuranceBet, setInsuranceBet] = useState<boolean>(false); + const [updatingInsurance, setUpdatingInsurance] = useState(false); + + + const getPuncherDisplayName = (race: RaceDetailData["race"]) => { + }
Removed / Before Commit
- diff --git a/src/modules/horse/pages/Race/stats.tsx b/src/modules/horse/pages/Race/stats.tsx - index a374cbd..e3ae538 100644 - const formattedBetRows = useMemo(() => { - return betRows.map((bet: any) => { - const oddsType = (bet.oddsType || bet.odds_type || bet.betType || '-').toString().toUpperCase(); - return { - id: bet._id || bet.id, - tenantName: bet.tenantName || bet.tenant_name || bet.tenant?.name || bet.platformName || '-', - oddsType: oddsType, - oddsRequest: bet.oddsRequest || bet.odds_request || bet.requestedOdds || '-', - betAmount: bet.betAmount || bet.bet_amount || bet.stake || '-', - }; - }); - }, [betRows]); - </tr> - ) : ( - formattedBetRows.map((row) => ( - <tr key={row.id}>
Added / After Commit
+ diff --git a/src/modules/horse/pages/Race/stats.tsx b/src/modules/horse/pages/Race/stats.tsx + index a374cbd..e3ae538 100644 + const formattedBetRows = useMemo(() => { + return betRows.map((bet: any) => { + const oddsType = (bet.oddsType || bet.odds_type || bet.betType || '-').toString().toUpperCase(); + const betStatus = String(bet.bet_status || bet.betStatus || "").toUpperCase(); + return { + id: bet._id || bet.id, + tenantName: bet.tenantName || bet.tenant_name || bet.tenant?.name || bet.platformName || '-', + oddsType: oddsType, + oddsRequest: bet.oddsRequest || bet.odds_request || bet.requestedOdds || '-', + betAmount: bet.betAmount || bet.bet_amount || bet.stake || '-', + betStatus: betStatus, + }; + }); + }, [betRows]); + </tr> + ) : (
Removed / Before Commit
- diff --git a/src/modules/horse/utils/formatters.tsx b/src/modules/horse/utils/formatters.tsx - index 371d8b8..f07e5a1 100644 - value: number, - type: "profit" | "loss" | "default" - ) => { - const absValue = Math.abs(value); - - switch (type) { - case "profit": - return `+${absValue}`; - case "loss": - return `-${absValue}`; - default: - return `${value}`; - } - }; - \ No newline at end of file
Added / After Commit
+ diff --git a/src/modules/horse/utils/formatters.tsx b/src/modules/horse/utils/formatters.tsx + index 371d8b8..f07e5a1 100644 + value: number, + type: "profit" | "loss" | "default" + ) => { + const absValue = Math.abs(value).toFixed(2); + + switch (type) { + case "profit": + return `+${absValue}`; + + case "loss": + return `-${absValue}`; + + default: + return `${value.toFixed(2)}`; + } + };
Removed / Before Commit
- diff --git a/src/pages/Platforms/AdminMarketPage.tsx b/src/pages/Platforms/AdminMarketPage.tsx - index 0f29017..9fae0c5 100644 - normalizedName.includes("PLACE") - ); - }; - const isFancyMarketType = (marketType?: string, marketName?: string) => { - const normalizedType = normalizeMarketType(marketType); - const normalizedName = normalizeMarketType(marketName); - - const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set()); - const [globalStates, setGlobalStates] = useState<Record<string, GlobalState>>({}); - const assignedRacesLoadedRef = useRef(false); - const permissionsKey = permissions.join(","); - - setGlobalStates({}); - setMarketDrafts({}); - setRunnerDrafts({}); - }
Added / After Commit
+ diff --git a/src/pages/Platforms/AdminMarketPage.tsx b/src/pages/Platforms/AdminMarketPage.tsx + index 0f29017..9fae0c5 100644 + normalizedName.includes("PLACE") + ); + }; + const isPlaceMarketType = (marketType?: string, marketName?: string) => { + const normalizedType = normalizeMarketType(marketType); + const normalizedName = normalizeMarketType(marketName); + + return ( + !isWinnerMarketType(marketType, marketName) && + (normalizedType.includes("PLACE") || normalizedName.includes("PLACE")) + ); + }; + const isFancyMarketType = (marketType?: string, marketName?: string) => { + const normalizedType = normalizeMarketType(marketType); + const normalizedName = normalizeMarketType(marketName); +
Removed / Before Commit
- diff --git a/src/pages/Platformuser/AllPlatformUsers.tsx b/src/pages/Platformuser/AllPlatformUsers.tsx - index 46d0ed9..4f102e5 100644 - const [currentPage, setCurrentPage] = useState(1); - const [perPage, setPerPage] = useState(10); - const [totalRows, setTotalRows] = useState(0); - const canAccessPlatformUsers = userRole === "PLATFORM_ADMIN" && permissions.length === 0; - const canViewUsers = canAccessPlatformUsers || hasPermissionAccess(permissions, "user.view"); - const canCreateUsers = canAccessPlatformUsers || hasPermissionAccess(permissions, "user.create"); - }); - }; - - const columns = [ - { - name: "Name", - selector: (row: PlatformUser) => row.username || "-", - }, - { - name: "Permissions",
Added / After Commit
+ diff --git a/src/pages/Platformuser/AllPlatformUsers.tsx b/src/pages/Platformuser/AllPlatformUsers.tsx + index 46d0ed9..4f102e5 100644 + const [currentPage, setCurrentPage] = useState(1); + const [perPage, setPerPage] = useState(10); + const [totalRows, setTotalRows] = useState(0); + const [expandedUserId, setExpandedUserId] = useState<string | null>(null); + const canAccessPlatformUsers = userRole === "PLATFORM_ADMIN" && permissions.length === 0; + const canViewUsers = canAccessPlatformUsers || hasPermissionAccess(permissions, "user.view"); + const canCreateUsers = canAccessPlatformUsers || hasPermissionAccess(permissions, "user.create"); + }); + }; + + const handleTogglePermissions = (userId: string) => { + setExpandedUserId(expandedUserId === userId ? null : userId); + }; + + const columns = [ + {
Removed / Before Commit
- diff --git a/src/pages/Platformuser/PlatformUseredit.tsx b/src/pages/Platformuser/PlatformUseredit.tsx - index fc6468c..5fdfd16 100644 - if (hasPunchingAll || !hasPunchingChild) { - return permissionsWithoutPunchingParent; - } - - const firstPunchingChildIndex = permissionsWithoutPunchingParent.findIndex(isPunchingChildPermission); - - return [ - const navigate = useNavigate(); - const location = useLocation(); - const { id } = useParams<{ id: string }>(); - const { userRole } = useAuth(); - const [loading, setLoading] = useState(false); - const [saving, setSaving] = useState(false); - const [editForm, setEditForm] = useState<EditFormState>(emptyForm); - }); -
Added / After Commit
+ diff --git a/src/pages/Platformuser/PlatformUseredit.tsx b/src/pages/Platformuser/PlatformUseredit.tsx + index fc6468c..5fdfd16 100644 + if (hasPunchingAll || !hasPunchingChild) { + return permissionsWithoutPunchingParent; + } + + const firstPunchingChildIndex = permissionsWithoutPunchingParent.findIndex(isPunchingChildPermission); + + return [ + const navigate = useNavigate(); + const location = useLocation(); + const { id } = useParams<{ id: string }>(); + const { userRole, refreshPermissions } = useAuth(); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + const [editForm, setEditForm] = useState<EditFormState>(emptyForm); + }); +
Removed / Before Commit
- diff --git a/src/pages/Races/stats.tsx b/src/pages/Races/stats.tsx - index 9a22f80..18fdfcd 100644 - oddsType: string; - oddsRequest: string; - betAmount: string; - }; - - const toNullableNumber = (value: string | number | null | undefined): number | null => { - ? "-" - : Number(value).toFixed(2); - - const formatExposure = (value: number) => (value > 0 ? `+${value}` : `${value}`); - const formatTextValue = (value: string | number | null | undefined) => - value === null || value === undefined || value === "" ? "-" : String(value); - - oddsType: formatTextValue(bet.odds_type), - oddsRequest: formatTextValue(bet.odds_request), - betAmount: formatTextValue(bet.bet_amount),
Added / After Commit
+ diff --git a/src/pages/Races/stats.tsx b/src/pages/Races/stats.tsx + index 9a22f80..18fdfcd 100644 + oddsType: string; + oddsRequest: string; + betAmount: string; + betStatus?: string; + }; + + const toNullableNumber = (value: string | number | null | undefined): number | null => { + ? "-" + : Number(value).toFixed(2); + + // const formatExposure = (value: number) => (value > 0 ? `+${value}` : `${value}`); + const formatExposure = (value: number) => + value > 0 ? `+${value.toFixed(2)}` : `${value.toFixed(2)}`; + + const formatTextValue = (value: string | number | null | undefined) => + value === null || value === undefined || value === "" ? "-" : String(value);
Removed / Before Commit
- diff --git a/src/services/api/authService.ts b/src/services/api/authService.ts - index 9dffb96..a455d22 100644 - "Authorization" - ] = `Bearer ${token}`; - - Permissions refreshed after login"); - return { token, user }; - } catch (error: any) { - console.error( - } - } - - Admin Profile Response:", response.data); - Updated userData with fresh permissions:", updatedUser); - /** - * Initialize auth (Call this once in App.tsx) - * Automatically attach token if exists
Added / After Commit
+ diff --git a/src/services/api/authService.ts b/src/services/api/authService.ts + index 9dffb96..a455d22 100644 + "Authorization" + ] = `Bearer ${token}`; + + // 🔥 Fetch fresh permissions from get-admin-profile API after login + try { + await this.getAdminProfile(); + console.log("✠+ Permissions refreshed after login"); + } catch (error) { + console.warn("âš ï¸ Failed to fetch admin profile after login:", error); + // Don't fail login if profile fetch fails + } + + return { token, user }; + } catch (error: any) { + console.error(
Removed / Before Commit
- diff --git a/src/services/api/raceService.ts b/src/services/api/raceService.ts - index 17d8fe2..5db9200 100644 - betfairEventId?: string; - raceType?: string; - status: boolean; - liveUrl?: string; - createdAt: string; - updatedAt?: string;
Added / After Commit
+ diff --git a/src/services/api/raceService.ts b/src/services/api/raceService.ts + index 17d8fe2..5db9200 100644 + betfairEventId?: string; + raceType?: string; + status: boolean; + insurance_bet?: boolean; + liveUrl?: string; + createdAt: string; + updatedAt?: string;