Skip to content

PROD - gamification / skills management consolidation. SSO users option #1123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 26, 2025

Conversation

jmgasper
Copy link
Collaborator

suppermancool and others added 11 commits June 24, 2025 15:30
Topcoder Admin App - Consolidate Gamification
Conflicts:
	src/apps/admin/src/admin-app.routes.tsx
	src/apps/admin/src/config/routes.config.ts
	src/apps/admin/src/lib/components/common/Layout/Layout.tsx
	src/apps/admin/src/lib/components/common/Tab/config/system-admin-tabs-config.ts
Topcoder Admin App - Add SSOLogin to User Management
Consolidate gamification, skills manager, add SSO users option in system-admin app
@@ -14,6 +14,7 @@ const AdminApp: FC = () => {
const { getChildRoutes }: RouterContextData = useContext(routerContext)
// eslint-disable-next-line react-hooks/exhaustive-deps -- missing dependency: getChildRoutes
const childRoutes = useMemo(() => getChildRoutes(toolTitle), [])
const { Layout }: { Layout: FC<LayoutProps> } = useLayout()

Choose a reason for hiding this comment

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

Consider adding error handling for the useLayout() hook in case it returns an unexpected value or fails to provide the Layout component.

@@ -1,3 +1,4 @@
import { baseDetailPath, createBadgePath } from '~/apps/gamification-admin'

Choose a reason for hiding this comment

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

The import statement for baseDetailPath and createBadgePath should be checked to ensure these paths are correct and that the modules exist in the specified location. If these imports are not used in the file, consider removing them to keep the code clean.

rootRoute,
userManagementRouteId,
} from './config/routes.config'
import { platformSkillRouteId } from './platform/routes.config'

Choose a reason for hiding this comment

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

Consider organizing the imports alphabetically for better readability and consistency.

{
element: <SkillManagementLandingPage />,
id: 'skills-landing-page',
route: '',

Choose a reason for hiding this comment

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

Consider specifying a more descriptive route path instead of an empty string for better clarity and maintainability.

rootPage={`${rootRoute}/${platformRouteId}/${gamificationAdminRouteId}`}
/>
),
route: gamificationAdminRouteId,

Choose a reason for hiding this comment

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

Ensure that the rootPage prop is correctly constructed and that all variables used in the template literal are defined and imported as needed.

},
{
element: <CreateBadgePage />,
route: `${gamificationAdminRouteId}${createBadgePath}`,

Choose a reason for hiding this comment

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

Verify that createBadgePath is defined and correctly imported to avoid runtime errors.

},
{
element: <BadgeDetailPage />,
route: `${gamificationAdminRouteId}${baseDetailPath}/:id`,

Choose a reason for hiding this comment

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

Ensure that baseDetailPath is defined and correctly imported. Also, confirm that the dynamic segment :id is handled properly in the application logic.

@@ -67,7 +67,7 @@ export const DialogEditUserEmail: FC<Props> = (props: Props) => {
className={classNames(styles.container, props.className)}
onSubmit={handleSubmit(onSubmit)}
>
<div>
<div className={styles.blockForm}>

Choose a reason for hiding this comment

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

Consider verifying if the styles.blockForm class is defined and used correctly in the stylesheet. Ensure it does not conflict with existing styles or cause unintended layout changes.

@@ -0,0 +1,71 @@
.modal {
width: 800px !important;

Choose a reason for hiding this comment

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

Avoid using !important unless absolutely necessary, as it can make the CSS harder to maintain and override.

align-items: flex-start;

th:first-child {
padding-left: 16px !important;

Choose a reason for hiding this comment

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

Using !important should be avoided if possible. Consider refactoring the CSS to achieve the desired effect without it.

}

.tableCell {
white-space: break-spaces !important;

Choose a reason for hiding this comment

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

The use of !important can lead to specificity issues in CSS. Try to refactor the styles to avoid using it.


.tableCell {
white-space: break-spaces !important;
text-align: left !important;

Choose a reason for hiding this comment

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

Consider removing !important to maintain cleaner and more manageable CSS.

width: 100%;
}

.btnActions {

Choose a reason for hiding this comment

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

The .btnActions class appears to have the same styles as .actionButtons. Consider consolidating these classes if they serve the same purpose.

height: 100px;

.spinner {
background: none;

Choose a reason for hiding this comment

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

The .spinner class is defined twice with the same background: none; style. Consider consolidating these styles to avoid redundancy.

left: 0;

.spinner {
background: none;

Choose a reason for hiding this comment

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

The .spinner class is defined twice with the same background: none; style. Consider consolidating these styles to avoid redundancy.

props.setOpen(false)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading])

Choose a reason for hiding this comment

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

The useCallback dependency array should include props.setOpen to ensure the function is correctly memoized with the latest reference.

type: 'action',
},
],
[isAdding, isRemoving, doRemoveSSOUserLogin],

Choose a reason for hiding this comment

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

The useMemo dependency array should include setShowEditForm to ensure the memoized value is updated when setShowEditForm changes.

]
}),
[columns],
)

Choose a reason for hiding this comment

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

The useMemo dependency array should include styles to ensure the memoized value is updated when styles changes.

const cancelEditForm = useCallback(() => {
setShowEditForm(undefined)
setShowAddForm(false)
}, [setShowAddForm, setShowEditForm])

Choose a reason for hiding this comment

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

The useCallback dependency array should include setShowAddForm and setShowEditForm to ensure the function is correctly memoized with the latest references.

columns={columns}
data={ssoUserLogins}
disableSorting
onToggleSort={_.noop}

Choose a reason for hiding this comment

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

The onToggleSort prop is set to _.noop, which might be unnecessary if sorting is disabled. Consider removing it if it's not needed.

userId: props.editingData.userId,
})
}
}, [props.editingData])

Choose a reason for hiding this comment

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

Consider adding reset to the dependency array to avoid potential stale closures or unintended behavior when the reset function changes.

placeholder='Select'
options={providerOptions}
value={controlProps.field.value}
onChange={controlProps.field.onChange}

Choose a reason for hiding this comment

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

The onChange handler for InputSelectReact should also handle null or undefined values to prevent potential runtime errors.

@@ -47,6 +50,7 @@ interface Props {

export const UsersTable: FC<Props> = props => {
const [colWidth, setColWidth] = useState<colWidthType>({})
const [ssoLoginProviders, setSsoLoginProviders] = useState<SSOLoginProvider[]>([])

Choose a reason for hiding this comment

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

Consider initializing ssoLoginProviders with a meaningful default value if applicable, or add a check before using it to ensure it doesn't lead to unexpected behavior when empty.

@@ -359,6 +370,16 @@ export const UsersTable: FC<Props> = props => {
[isTablet, isMobile],
)

useOnComponentDidMount(() => {

Choose a reason for hiding this comment

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

Consider adding error handling logic within the fetchSSOLoginProviders function itself to encapsulate error management, rather than handling it externally in the component.

setSsoLoginProviders(result)
})
.catch(e => {
handleError(e)

Choose a reason for hiding this comment

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

Ensure that the handleError function provides meaningful feedback to the user or logs the error appropriately for debugging purposes.

{showDialogEditUserSSOLogin && (
<DialogEditUserSSOLogin
open
setOpen={function setOpen() {

Choose a reason for hiding this comment

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

Consider using a more descriptive function name for setOpen to clearly indicate its purpose, such as closeSSOLoginDialog.


@include ltelg {
padding: 0;
}

Choose a reason for hiding this comment

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

The class name .contantentLayoutInner seems to be a typo. It has been corrected to .contentLayoutInner, which is good. Ensure that all references to this class in the codebase are updated accordingly.

)
export const NullLayout: FC<PropsWithChildren> = props => <>{props.children}</>

export type LayoutProps = PropsWithChildren<{

Choose a reason for hiding this comment

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

The comment // eslint-disable-line react/no-unused-prop-types -- it's actually used suggests that the classes prop is being used, but it's not clear from the code why this comment is necessary. Consider removing the comment if it's not needed or providing more context on why the ESLint rule is being disabled.

<>{props.children}</>
)
export const NullLayout: FC<PropsWithChildren> = props => <>{props.children}</>

Choose a reason for hiding this comment

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

The NullLayout component is defined on a single line, which can reduce readability. Consider formatting it across multiple lines for better clarity and consistency with other component definitions.

)

export function useLayout(): { Layout: FC<LayoutProps> } {
const routerContextData: RouterContextData = useContext(routerContext)

Choose a reason for hiding this comment

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

The useLayout function uses useContext to access routerContext, but there is no error handling if routerContext is not available. Consider adding a check or a fallback to handle cases where routerContext might be undefined.

? `/${platformRouteId}/${platformSkillRouteId}`
: `/${AppSubdomain.admin}/${platformRouteId}/${platformSkillRouteId}`

if (window.location.pathname.toLowerCase()

Choose a reason for hiding this comment

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

The use of window.location.pathname.toLowerCase() could potentially lead to issues if the pathname contains encoded characters. Consider using a more robust method to handle path comparisons.

@@ -3,11 +3,14 @@ import _ from 'lodash'
import { TabsNavItem } from '~/libs/ui'
import {
billingAccountRouteId,
gamificationAdminRouteId,

Choose a reason for hiding this comment

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

Consider verifying if gamificationAdminRouteId is correctly defined and used in the application, as it is newly added.

manageChallengeRouteId,
manageReviewRouteId,
permissionManagementRouteId,
platformRouteId,

Choose a reason for hiding this comment

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

Ensure that platformRouteId is correctly integrated and utilized within the application, as it is a new addition.

userManagementRouteId,
} from '~/apps/admin/src/config/routes.config'
import { platformSkillRouteId } from '~/apps/admin/src/platform/routes.config'

Choose a reason for hiding this comment

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

Check if platformSkillRouteId is properly defined and used, as it is newly imported from a different module.

{
children: [
{

Choose a reason for hiding this comment

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

Remove the extra newline here to maintain consistent formatting with other entries.

{
id: `${platformRouteId}/${gamificationAdminRouteId}`,
title: 'Badges',

Choose a reason for hiding this comment

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

Remove the extra newline here to maintain consistent formatting with other entries.

case SSOUserLoginsActionType.UPDATE_SSO_USER_LOGIN_INIT: {
return {
...previousState,
isAdding: true,

Choose a reason for hiding this comment

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

The isAdding state is being used for both adding and updating SSO user logins. Consider using a separate state variable for updating to avoid confusion and potential bugs.

...previousState,
isAdding: false,
ssoUserLogins: previousState.ssoUserLogins.map(
item => (item.provider === action.payload.provider

Choose a reason for hiding this comment

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

The comparison item.provider === action.payload.provider assumes that provider is a unique identifier for SSOUserLogin. Ensure that provider is indeed unique or consider using a more reliable identifier for matching.


case SSOUserLoginsActionType.REMOVE_SSO_USER_LOGIN_DONE: {
const ssoUserLogins = _.filter(
previousState.ssoUserLogins,

Choose a reason for hiding this comment

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

Using _.filter from lodash is fine, but since this is a simple filtering operation, consider using the native JavaScript filter method for better readability and performance.

@@ -1,15 +1,18 @@
import _ from 'lodash'

import { EnvironmentConfig } from '~/config'
import { xhrGetAsync, xhrPatchAsync } from '~/libs/core'
import { xhrDeleteAsync, xhrGetAsync, xhrPatchAsync, xhrPostAsync, xhrPutAsync } from '~/libs/core'

Choose a reason for hiding this comment

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

Consider removing the unused import xhrPutAsync if it is not needed in this file.


import {
adjustUserInfoResponse,
adjustUserStatusHistoryResponse,
ApiV3Response,
SSOLoginProvider,

Choose a reason for hiding this comment

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

The SSOLoginProvider import is added but not used in the current code. Ensure it is necessary or remove it if not used.


import {
adjustUserInfoResponse,
adjustUserStatusHistoryResponse,
ApiV3Response,
SSOLoginProvider,
SSOUserLogin,

Choose a reason for hiding this comment

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

The SSOUserLogin import is added but not used in the current code. Ensure it is necessary or remove it if not used.

UserInfo,
UserStatusHistory,
} from '../models'
import { FormAddSSOLoginData } from '../models/FormAddSSOLoginData.model'

Choose a reason for hiding this comment

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

The FormAddSSOLoginData import is added but not used in the current code. Ensure it is necessary or remove it if not used.

}

/**
* Fetch list of sso login provider.

Choose a reason for hiding this comment

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

The comment for the fetchSSOLoginProviders function states that it returns 'sso user logins', but it should be 'sso login providers'.

* @param userId user id.
* @param userLogin user login info.
* @returns resolves to sso user login
*/

Choose a reason for hiding this comment

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

The comment for the createSSOUserLogin function states that it returns 'sso user login', but it should specify that it returns the created 'sso user login'.

* Update sso user login.
* @param userId user id.
* @param userLogin user login info.
* @returns resolves to sso user login

Choose a reason for hiding this comment

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

The comment for the updateSSOUserLogin function states that it returns 'sso user login', but it should specify that it returns the updated 'sso user login'.

* Delete sso user login.
* @param userId user id.
* @param provider login provider.
* @returns resolves to sso user login

Choose a reason for hiding this comment

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

The comment for the deleteSSOUserLogin function states that it returns 'sso user login', but it should specify that it returns the deleted 'sso user login'.

*/
export const formAddSSOLoginSchema: Yup.ObjectSchema<FormAddSSOLoginData>
= Yup.object({
email: Yup.string()

Choose a reason for hiding this comment

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

Consider adding a .max() constraint to limit the length of the email string to a reasonable number of characters, as email addresses have a practical length limit.

.trim()
.email('Invalid email address.')
.required('Email address is required.'),
name: Yup.string()

Choose a reason for hiding this comment

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

Consider adding a .max() constraint to limit the length of the name string to prevent excessively long inputs.

name: Yup.string()
.trim()
.required('Name is required.'),
provider: Yup.string()

Choose a reason for hiding this comment

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

Consider adding a .max() constraint to limit the length of the provider string to prevent excessively long inputs.

provider: Yup.string()
.trim()
.required('Provider is required.'),
userId: Yup.string()

Choose a reason for hiding this comment

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

Consider adding a .max() constraint to limit the length of the userId string to prevent excessively long inputs.

() => adminRoutes[0].children
?.find(r => r.id === platformRouteId)
?.children?.map(getRouteElement),
[], // eslint-disable-line react-hooks/exhaustive-deps -- missing dependency: getRouteElement

Choose a reason for hiding this comment

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

The useMemo dependency array is empty, which might lead to stale closures if getRouteElement or adminRoutes change. Consider adding getRouteElement and adminRoutes as dependencies to ensure the memoized value is updated correctly.


import { SkillsManagerContextValue, useSkillsManagerContext } from '../context'
import { SkillsManagerContextValue, useSkillsManagerContext } from '../lib/context'

Choose a reason for hiding this comment

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

The import path for useSkillsManagerContext has been changed from ../context to ../lib/context. Ensure that the new path is correct and that the context module has been moved to the lib directory.

@@ -30,7 +30,7 @@ const LandingPage: FC<{}> = () => {
}: SkillsManagerContextValue = useSkillsManagerContext()

return (
<ContentLayout innerClass={styles.contentWrap}>
<div className={styles.contentWrap}>

Choose a reason for hiding this comment

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

Suggestion:

Consider using a semantic HTML element like <main> instead of <div> for the main content area to improve accessibility and provide better context for the content structure.

@@ -63,7 +63,7 @@ const LandingPage: FC<{}> = () => {
)}

{!!showSkillModal && <SkillModal skill={showSkillModal} />}
</ContentLayout>
</div>

Choose a reason for hiding this comment

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

The closing tag for ContentLayout has been replaced with a div. Ensure that this change is intentional and that the div provides the same styling and layout functionality as ContentLayout. If ContentLayout has specific styles or logic, consider whether those need to be replicated or replaced.

?.find(r => r.id === platformRouteId)
?.children?.find(r => r.id === platformSkillRouteId)
?.children?.map(getRouteElement),
[], // eslint-disable-line react-hooks/exhaustive-deps -- missing dependency: getRouteElement

Choose a reason for hiding this comment

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

Using an empty dependency array [] in useMemo means that the memoized value will only be recalculated once, when the component mounts. If getRouteElement or any other variables used inside the useMemo function can change, they should be included in the dependency array to ensure the memoized value updates correctly.

@@ -52,7 +53,7 @@ const BadgeCreatedModal: FC<BadgeCreatedModalProps> = (props: BadgeCreatedModalP
label='View'
primary
size='lg'
to={badgeDetailPath(props.badge.id)}
to={badgeDetailPath(props.rootPage, props.badge.id)}

Choose a reason for hiding this comment

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

The badgeDetailPath function signature seems to have changed to accept an additional parameter props.rootPage. Ensure that this change is reflected in the function definition and all its usages throughout the codebase to prevent potential runtime errors.

export function badgeDetailPath(badgeId: string, view?: 'edit' | 'award'): string {
return `${rootRoute}${baseDetailPath}/${badgeId}${!!view ? `#${view}` : ''}`
export function badgeDetailPath(
rootPage: string,

Choose a reason for hiding this comment

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

The function badgeDetailPath now requires an additional rootPage parameter. Ensure that all calls to this function in the codebase are updated to pass the correct rootPage value.

route: rootRoute,
},
]
export const createBadgeRoute: (rootPage: string) => string = (

Choose a reason for hiding this comment

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

The createBadgeRoute is now a function that takes rootPage as a parameter. Ensure that all usages of createBadgeRoute are updated to call it as a function with the appropriate rootPage argument.

@@ -20,8 +21,6 @@ import MarkdownIt from 'markdown-it'
import sanitizeHtml from 'sanitize-html'

import {

Choose a reason for hiding this comment

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

The Breadcrumb and BreadcrumbItemModel imports have been removed. Ensure that these components are no longer used in the rest of the file to avoid runtime errors.

interface Props extends GameBadge {
rootPage: string;
}
const BadgeDetailPage: FC<Props> = (props: Props) => {

Choose a reason for hiding this comment

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

The BadgeDetailPage component now takes Props as an argument, which extends GameBadge. Ensure that all required properties from GameBadge are being passed correctly when this component is used.

rootPage: string;
}
const BadgeDetailPage: FC<Props> = (props: Props) => {
const initColumns = useMemo(() => badgeListingColumns(props.rootPage), [props.rootPage])

Choose a reason for hiding this comment

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

The useMemo hook is used to memoize initColumns. Verify that badgeListingColumns is a pure function to ensure that useMemo works as expected and does not cause unnecessary re-renders.

}
const BadgeDetailPage: FC<Props> = (props: Props) => {
const initColumns = useMemo(() => badgeListingColumns(props.rootPage), [props.rootPage])
const navigate = useNavigate()

Choose a reason for hiding this comment

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

The useNavigate hook is used here. Ensure that the react-router-dom version being used supports this hook, as it was introduced in version 6.

@@ -106,7 +102,7 @@ const BadgeDetailPage: FC = () => {
= useState<boolean>(false)

// badgeListingMutate will reset badge listing page cache when called
const sort: Sort = tableGetDefaultSort(badgeListingColumns)
const sort: Sort = tableGetDefaultSort(initColumns)

Choose a reason for hiding this comment

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

The variable initColumns is used here, but it's not clear from the diff if initColumns is defined or imported correctly. Please ensure that initColumns is properly defined or imported in the file to avoid potential runtime errors.

@@ -153,6 +149,7 @@ const BadgeDetailPage: FC = () => {
onBadgeUpdated()
})
}
// eslint-disable-next-line react-hooks/exhaustive-deps

Choose a reason for hiding this comment

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

Disabling the react-hooks/exhaustive-deps rule can lead to potential issues with dependencies in the useEffect hook. Consider ensuring that all necessary dependencies are included in the dependency array to avoid unexpected behavior.

@@ -175,6 +172,7 @@ const BadgeDetailPage: FC = () => {
default: break
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps

Choose a reason for hiding this comment

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

Disabling the react-hooks/exhaustive-deps rule can lead to potential issues with dependencies not being properly tracked in the useEffect hook. Consider explicitly listing all dependencies that the effect relies on to ensure it behaves as expected.

@@ -201,6 +199,7 @@ const BadgeDetailPage: FC = () => {
setShowActivatedModal(true)
onBadgeUpdated()
})
// eslint-disable-next-line no-alert
.catch(e => alert(`onActivateBadge error: ${e.message}`))

Choose a reason for hiding this comment

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

Using alert for error handling is not recommended as it can be disruptive to the user experience. Consider using a more user-friendly notification system or logging the error for debugging purposes.

@@ -217,6 +216,7 @@ const BadgeDetailPage: FC = () => {
setShowActivatedModal(true)
onBadgeUpdated()
})
// eslint-disable-next-line no-alert

Choose a reason for hiding this comment

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

Using alert for error handling is not recommended as it can be disruptive to the user experience. Consider using a more user-friendly notification system or logging the error for debugging purposes.

secondaryButtonConfig={{
label: 'Back',
onClick: () => {
navigate('./../../')

Choose a reason for hiding this comment

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

Consider using navigate(-1) to go back to the previous page in the history stack, which is more robust than using a relative path.

@@ -1,4 +1,4 @@
import { Dispatch, FC, SetStateAction, useState } from 'react'
import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react'

Choose a reason for hiding this comment

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

The useMemo hook is imported but not used in the code. If it's not needed, consider removing it to keep the imports clean and avoid confusion.

interface Props extends GameBadge {
rootPage: string;
}
const BadgeListingPage: FC<Props> = (props: Props) => {

Choose a reason for hiding this comment

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

The props parameter is explicitly typed as Props, which is redundant since TypeScript can infer the type from the function signature. Consider removing the explicit type annotation for props.

rootPage: string;
}
const BadgeListingPage: FC<Props> = (props: Props) => {
const initColumns = useMemo(() => badgeListingColumns(props.rootPage), [props.rootPage])

const [sort, setSort]: [Sort, Dispatch<SetStateAction<Sort>>]

Choose a reason for hiding this comment

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

The useMemo hook is used to memoize initColumns, which is good for performance. However, ensure that badgeListingColumns is a pure function and does not have side effects, as this could lead to unexpected behavior.

@@ -36,7 +40,7 @@ const BadgeListingPage: FC = () => {
// header button config
const buttonConfig: ButtonProps = {
label: 'Create New Badge',
onClick: () => navigate(createBadgeRoute),
onClick: () => navigate(createBadgeRoute(props.rootPage)),

Choose a reason for hiding this comment

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

The navigate function now takes an argument props.rootPage, but props is not defined or imported in the current scope. Ensure that props is correctly defined or passed to the component.

const BadgeActionRenderer: (badge: GameBadge) => JSX.Element
= (badge: GameBadge): JSX.Element => {
interface Props extends GameBadge {
rootPage: string;

Choose a reason for hiding this comment

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

Consider renaming the rootPage property to something more descriptive if it represents a specific concept or functionality. This will improve code readability and maintainability.

}

const BadgeActionRenderer: (props: Props) => JSX.Element
= (props: Props): JSX.Element => {

const isMobile: boolean = useCheckIsMobile()

Choose a reason for hiding this comment

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

The isMobile variable is declared but not used in the provided diff. Ensure that it is utilized in the component or remove it if it is unnecessary.

@@ -33,7 +37,7 @@ const BadgeActionRenderer: (badge: GameBadge) => JSX.Element
size={isMobile ? 'sm' : 'md'}
key={button.label}
label={button.label}
to={badgeDetailPath(badge.id, button.view)}
to={badgeDetailPath(props.rootPage, props.id, button.view)}

Choose a reason for hiding this comment

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

The badgeDetailPath function signature seems to have changed, now requiring props.rootPage and props.id as arguments. Ensure that these props are correctly passed and available in the component. If props.id is intended to replace badge.id, verify that it holds the correct value.

@@ -5,17 +5,17 @@ import { GameBadge } from '../../../game-lib'
import { BadgeActionRenderer } from './badge-action-renderer'
import { BadgeListingNameRenderer } from './badge-name-renderer'

export const badgeListingColumns: ReadonlyArray<TableColumn<GameBadge>> = [
export const badgeListingColumns: (rootPage: string) => ReadonlyArray<TableColumn<GameBadge>> = (rootPage: string) => [

Choose a reason for hiding this comment

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

The change from a constant array to a function returning an array is correct, but ensure that all usages of badgeListingColumns are updated accordingly to pass the rootPage parameter.

{
defaultSortDirection: 'asc',
isDefaultSort: true,
label: 'Badge Name',
propertyName: 'badge_name',
renderer: BadgeListingNameRenderer,
renderer: (data: GameBadge) => <BadgeListingNameRenderer {...data} />,

Choose a reason for hiding this comment

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

The renderer function now uses JSX syntax to render BadgeListingNameRenderer. Ensure that BadgeListingNameRenderer is a valid React component and can accept the spread data props.

type: 'element',
},
{
renderer: BadgeActionRenderer,
renderer: (data: GameBadge) => <BadgeActionRenderer {...data} rootPage={rootPage} />,

Choose a reason for hiding this comment

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

The renderer function now uses JSX syntax to render BadgeActionRenderer with additional rootPage prop. Ensure that BadgeActionRenderer is a valid React component and can accept both the spread data props and the rootPage prop.

@@ -1,21 +1,18 @@
import { Dispatch, FC, SetStateAction, useState } from 'react'
import { Dispatch, FC, SetStateAction, useMemo, useState } from 'react'

Choose a reason for hiding this comment

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

The useMemo hook is imported but not used in a way that optimizes performance. Ensure that the computation inside useMemo is expensive enough to justify its use.

url: '#',
},
])
interface Props extends GameBadge {

Choose a reason for hiding this comment

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

The Props interface extends GameBadge, but it's not clear if all properties of GameBadge are necessary for CreateBadgePage. Consider explicitly defining only the required properties to improve code readability and maintainability.

<div className={styles.container}>
<CreateBadgeForm
formDef={createBadgeFormDef}
formDef={formDef}
onSave={onSave}

Choose a reason for hiding this comment

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

The variable formDef is used here, but it's not clear from the diff if it has been defined or imported correctly. Please ensure that formDef is properly defined or imported in the component.

onSave={onSave}
/>
</div>
{
createdBadge && (
<BadgeCreatedModal
rootPage={props.rootPage}

Choose a reason for hiding this comment

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

The prop rootPage is being added to BadgeCreatedModal. Ensure that BadgeCreatedModal component is updated to accept this new prop and handle it appropriately.

@@ -3,7 +3,6 @@ import { noop } from 'lodash'
import { FormDefinition, IconOutline, validatorRequired } from '~/libs/ui'

import { ACCEPTED_BADGE_MIME_TYPES, MAX_BADGE_IMAGE_FILE_SIZE } from '../../../config'
import { rootRoute } from '../../../gamification-admin.routes'

Choose a reason for hiding this comment

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

The import for rootRoute has been removed, but it is not clear if it was unused or if its removal was intentional. Please verify if this import is no longer needed or if it should be replaced with another import.

@@ -12,7 +11,7 @@ export enum CreateBadgeFormField {
file = 'file',
}

export const createBadgeFormDef: FormDefinition = {
export const createBadgeFormDef: (rootPage: string) => FormDefinition = (rootPage: string) => ({

Choose a reason for hiding this comment

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

The type annotation for the createBadgeFormDef function is repeated. Consider removing the redundant type annotation for the parameter rootPage: string as it is already inferred from the function signature.

@@ -28,7 +27,7 @@ export const createBadgeFormDef: FormDefinition = {
{
buttonStyle: 'secondary',
icon: IconOutline.ChevronLeftIcon,
route: rootRoute || '/',
route: rootPage,

Choose a reason for hiding this comment

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

The change from rootRoute || '/' to rootPage removes the fallback to the root path ('/'). Ensure that rootPage is always defined and valid, or consider maintaining a fallback to prevent potential navigation issues.

@@ -90,4 +89,4 @@ export const createBadgeFormDef: FormDefinition = {
},
renderGroupDividers: false,
},
}
} as FormDefinition)

Choose a reason for hiding this comment

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

The type assertion as FormDefinition is used here. Ensure that this is necessary and that the createBadgeFormDef object conforms to the FormDefinition type. If the object already matches the type, consider removing the type assertion to avoid unnecessary complexity.

@@ -1,9 +1,7 @@
export enum AppSubdomain {
skillsManager = 'manage',
accounts = 'account-settings',

Choose a reason for hiding this comment

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

The skillsManager subdomain has been removed. Ensure that this is intentional and that there are no dependencies or references to this subdomain elsewhere in the codebase.

accounts = 'account-settings',
devCenter = 'devcenter',
earn = 'earn',
gamificationAdmin = 'gamification-admin',
profiles = 'profiles',
tcAcademy = 'academy',

Choose a reason for hiding this comment

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

The gamificationAdmin subdomain has been removed. Verify that this removal is intentional and that there are no remaining dependencies or references to this subdomain in other parts of the application.

@@ -92,7 +92,9 @@ const TabsNavbar: FC<TabsNavbarProps> = (props: TabsNavbarProps) => {
initialTab.current = ''
} else if (props.defaultActive) {
setTabOpened(props.defaultActive)
updateOffset(props.defaultActive)
setTimeout(() => {

Choose a reason for hiding this comment

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

The use of setTimeout to delay the updateOffset function call may introduce potential issues such as race conditions or unexpected behavior if the component's state changes during the delay. Consider providing a rationale for this change or exploring alternative solutions that do not rely on arbitrary time delays.

@jmgasper jmgasper merged commit 090a9fa into master Jun 26, 2025
3 of 4 checks passed
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