New work app to replace work-manager, and v6 projects API usage in copilots app#1487
New work app to replace work-manager, and v6 projects API usage in copilots app#1487
Conversation
| @@ -0,0 +1,5 @@ | |||
| REACT_APP_GROUPS_API_URL=https://api.topcoder.com/v6/groups | |||
| REACT_APP_TERMS_API_URL=https://api.topcoder.com/v5/terms | |||
There was a problem hiding this comment.
[❗❗ correctness]
The API version for REACT_APP_TERMS_API_URL is v5, while others are v6. Ensure this is intentional and that the correct API version is being used.
| height: 400, | ||
| menubar: false, | ||
| plugins: ['table', 'link', 'textcolor', 'contextmenu'], | ||
| plugins: ['table', 'link'], |
There was a problem hiding this comment.
[correctness]
The removal of the textcolor plugin from the TinyMCE editor configuration might impact users who rely on text color customization. If this change is intentional, ensure that there are no requirements for text color editing. Otherwise, consider re-adding the plugin to maintain existing functionality.
| height: 400, | ||
| menubar: false, | ||
| plugins: ['table', 'link', 'textcolor', 'contextmenu'], | ||
| plugins: ['table', 'link'], |
There was a problem hiding this comment.
[correctness]
The removal of the contextmenu plugin could affect the user experience by disabling right-click context menus in the editor. Verify if this change aligns with the desired functionality and user requirements.
| const viewRequestDetails = useMemo(() => ( | ||
| routeParams.requestId && find(requests, { id: +routeParams.requestId }) as CopilotRequest | ||
| routeParams.requestId | ||
| ? find(requests, request => `${request.id}` === routeParams.requestId) as CopilotRequest |
There was a problem hiding this comment.
[💡 performance]
The use of string interpolation to compare request.id with routeParams.requestId could be avoided if routeParams.requestId is already a string. Consider ensuring request.id is a string when stored or retrieved, or use a more explicit conversion method if necessary.
| import { CopilotRequest } from '../models/CopilotRequest' | ||
|
|
||
| const baseUrl = `${EnvironmentConfig.API.V5}/projects` | ||
| const baseUrl = `${EnvironmentConfig.API.V6}/projects` |
There was a problem hiding this comment.
[❗❗ correctness]
Ensure that all endpoints and related logic are compatible with the new API version V6. This change might require additional updates elsewhere in the codebase to handle any differences in API responses or behavior.
| createdAt: new Date(data.createdAt), | ||
| data: undefined, | ||
| opportunity: data.copilotOpportunity?.[0], | ||
| projectId: String(data?.projectId ?? data?.data?.projectId ?? ''), |
There was a problem hiding this comment.
[correctness]
The use of String(data?.projectId ?? data?.data?.projectId ?? '') could lead to unexpected results if projectId is 0 or false. Consider explicitly checking for undefined or null to avoid coercing falsy values.
|
|
||
| export type ProjectsResponse = SWRResponse<Project[], Project[]> | ||
|
|
||
| const sleep = (ms: number): Promise<()=> void> => new Promise(resolve => { setTimeout(resolve, ms) }) |
There was a problem hiding this comment.
[correctness]
The sleep function returns a Promise<() => void>, which is misleading because the resolved value is actually undefined. Consider changing the return type to Promise<void> for clarity.
| const sleep = (ms: number): Promise<()=> void> => new Promise(resolve => { setTimeout(resolve, ms) }) | ||
| const normalizeProject = (project: Project): Project => ({ | ||
| ...project, | ||
| id: String(project.id), |
There was a problem hiding this comment.
[correctness]
The normalizeProject function converts project.id to a string. Ensure that this conversion is necessary and that all parts of the application expect id to be a string. If id is used as a number elsewhere, this could lead to inconsistencies.
| export const PageWrapper: FC<PropsWithChildren<Props>> = props => ( | ||
| <div className={classNames(styles.container, props.className)}> | ||
| <BreadCrumb list={props.breadCrumb} /> | ||
| {props.breadCrumb.length > 0 && ( |
There was a problem hiding this comment.
[correctness]
The check props.breadCrumb.length > 0 is a good optimization to avoid rendering an empty BreadCrumb component. However, ensure that props.breadCrumb is always initialized as an array to prevent potential runtime errors.
| {props.pageTitle} | ||
| </h3> | ||
| </PageHeader> | ||
| {props.titleAction |
There was a problem hiding this comment.
[💡 readability]
The conditional rendering of props.titleAction using a ternary operator is correct, but the use of undefined as the fallback is unnecessary. Consider using a logical AND (&&) operator for cleaner code: {props.titleAction && <div className={styles.blockTitleAction}>{props.titleAction}</div>}.
|
|
||
| const WorkApp: FC = () => { | ||
| const { getChildRoutes }: RouterContextData = useContext(routerContext) | ||
| const childRoutes = useMemo(() => getChildRoutes(toolTitle), [getChildRoutes]) |
There was a problem hiding this comment.
[💡 performance]
Using useMemo here is unnecessary unless getChildRoutes is computationally expensive or has side effects. Consider removing useMemo if it's not needed for performance optimization.
| const childRoutes = useMemo(() => getChildRoutes(toolTitle), [getChildRoutes]) | ||
|
|
||
| useEffect(() => { | ||
| document.body.classList.add(WORK_APP_BODY_CLASS) |
There was a problem hiding this comment.
[maintainability]
Directly manipulating the document.body.classList can lead to unexpected side effects, especially if multiple components are modifying the class list. Consider using a state management solution or a library like classnames to manage body classes more predictably.
| export const TABLE_DATE_FORMAT = 'MMM DD YYYY, HH:mm A' | ||
|
|
||
| export const PAGINATION_PER_PAGE_OPTIONS = [ | ||
| { label: '5', value: '5' }, |
There was a problem hiding this comment.
[correctness]
The value in PAGINATION_PER_PAGE_OPTIONS is a string, but it might be more appropriate to use a number type for consistency and to avoid potential type-related issues when using these values for pagination logic.
| @@ -0,0 +1,28 @@ | |||
| import { AppSubdomain, EnvironmentConfig } from '~/config' | |||
|
|
|||
| export const rootRoute: string | |||
There was a problem hiding this comment.
[💡 maintainability]
Consider using a function to determine rootRoute instead of a ternary operator. This could improve readability and maintainability, especially if the logic becomes more complex in the future.
| const defaultDate = new Date() | ||
| defaultDate.setHours(0, 0, 0, 0) | ||
|
|
||
| const daysUntilSaturday = (SATURDAY_DAY_INDEX - defaultDate.getDay() + 7) % 7 |
There was a problem hiding this comment.
[❗❗ correctness]
The calculation for daysUntilSaturday could be simplified by using (SATURDAY_DAY_INDEX - defaultDate.getDay() + 7) % 7. This ensures that the result is always non-negative, which is crucial for correctly setting the date.
| const [remarks, setRemarks] = useState<string>('') | ||
| const [weekEndingDate, setWeekEndingDate] = useState<Date | null>(() => getDefaultWeekEndingDate()) | ||
|
|
||
| const paymentTitle = useMemo( |
There was a problem hiding this comment.
[correctness]
The useMemo hook for paymentTitle depends on weekEndingDate, props.projectName, and props.engagementName. Ensure that these dependencies are correctly updated to prevent stale values from being used.
| }, []) | ||
|
|
||
| useEffect(() => { | ||
| if (!props.open) { |
There was a problem hiding this comment.
[design]
The useEffect hook resets the state whenever props.open changes. Consider whether this is the desired behavior, as it might reset the form unexpectedly if open is toggled.
| customInput={<WeekEndingInput />} | ||
| dateFormat='MM/dd/yyyy' | ||
| disabled={isSubmitting} | ||
| filterDate={isSaturday} |
There was a problem hiding this comment.
[💡 usability]
The filterDate function isSaturday ensures that only Saturdays can be selected. This is a good constraint, but ensure that users are aware of this restriction, perhaps through UI hints or documentation.
| <ul className={styles.list}> | ||
| {paymentsResult.payments.map((payment, index) => { | ||
| const paymentStatus = getPaymentStatus(payment) | ||
| const normalizedPaymentStatus = paymentStatus |
There was a problem hiding this comment.
[maintainability]
The trim() and toLowerCase() operations on paymentStatus could be moved into getPaymentStatus if this normalization is always required. This would improve maintainability by centralizing the logic.
| import { xhrGetAsync } from '~/libs/core' | ||
|
|
||
| export interface BillingAccount { | ||
| active?: boolean |
There was a problem hiding this comment.
[correctness]
The active property is optional, but its type is not specified. Consider specifying a boolean type for clarity and to avoid potential type-related issues.
|
|
||
| export interface BillingAccount { | ||
| active?: boolean | ||
| endDate?: string |
There was a problem hiding this comment.
[correctness]
The endDate property is optional and typed as a string. Consider specifying the expected date format or using a Date object to ensure consistent handling of date values.
| endDate?: string | ||
| id: number | string | ||
| name: string | ||
| startDate?: string |
There was a problem hiding this comment.
[correctness]
The startDate property is optional and typed as a string. Consider specifying the expected date format or using a Date object to ensure consistent handling of date values.
| id: number | string | ||
| name: string | ||
| startDate?: string | ||
| status?: string |
There was a problem hiding this comment.
[maintainability]
The status property is optional and typed as a string. Consider defining an enumeration for possible status values to improve type safety and maintainability.
|
|
||
| await Promise.all( | ||
| projectIds.map(async projectId => { | ||
| if (projectNameByProjectId.has(projectId)) { |
There was a problem hiding this comment.
[correctness]
Consider checking if projectId is a valid string before using it in fetchProjectById. This could prevent unnecessary API calls with invalid IDs.
| projectNameByProjectId.set(projectId, projectName) | ||
| } | ||
| } catch { | ||
| // Project lookup failures should not break engagement loading. |
There was a problem hiding this comment.
[maintainability]
Swallowing all errors in the catch block without logging or handling them might make debugging difficult. Consider logging the error or handling specific error cases.
| } | ||
|
|
||
| const projectId = getEngagementProjectId(engagement) | ||
| const projectName = projectNameByProjectId.get(projectId) |
There was a problem hiding this comment.
[correctness]
Accessing projectNameByProjectId without checking if projectId is a valid string could lead to unexpected behavior. Ensure projectId is valid before using it as a key.
| } | ||
| } | ||
|
|
||
| if (typedError?.response?.status !== 403) { |
There was a problem hiding this comment.
[correctness]
The check for typedError?.response?.status !== 403 should be more explicit. Consider using typedError?.response?.status !== 403 to ensure the status is exactly 403, as other 4xx errors might also indicate authorization issues.
| } | ||
|
|
||
| if ( | ||
| normalizedBillingAccount.active === undefined |
There was a problem hiding this comment.
[💡 maintainability]
The condition in the if statement checks for multiple properties being undefined or falsy. Consider using a utility function to improve readability and maintainability.
|
|
||
| return extractProjectTypes(response) | ||
| } catch (error) { | ||
| if (hasAuthorizationMetadataError(error)) { |
There was a problem hiding this comment.
[correctness]
The fallback mechanism for fetching project types when an authorization error occurs is a good approach. However, ensure that the fallback URL (PROJECT_METADATA_API_URL) is correct and that it provides the expected data structure.
| size='sm' | ||
| /> | ||
| </Link> | ||
| {assignedStatus |
There was a problem hiding this comment.
[maintainability]
The conditional rendering of the assignedStatus block is duplicated for both leftActions and rightActions. Consider refactoring this logic to avoid repetition and improve maintainability.
|
|
||
| <PaymentFormModal | ||
| billingAccountId={projectResult.project?.billingAccountId} | ||
| engagementName={engagementResult.engagement?.title} |
There was a problem hiding this comment.
[💡 correctness]
Ensure that engagementResult.engagement?.title is always defined before passing it to PaymentFormModal. If there's a chance it could be undefined, consider providing a default value or handling the case where it is not available.
| onCancel={() => setPaymentMember(undefined)} | ||
| onConfirm={handlePaymentSubmit} | ||
| open={!!paymentMember} | ||
| projectName={projectResult.project?.name} |
There was a problem hiding this comment.
[💡 correctness]
Ensure that projectResult.project?.name is always defined before passing it to PaymentFormModal. If there's a chance it could be undefined, consider providing a default value or handling the case where it is not available.
| return ( | ||
| <PageWrapper | ||
| backUrl={backUrl} | ||
| breadCrumb={[]} |
There was a problem hiding this comment.
[maintainability]
The breadCrumb prop is now set to an empty array, which might lead to a loss of navigation context for users. If the breadcrumb functionality is intended to be removed, ensure that this change is communicated to the team and any dependent components or tests are updated accordingly.
| } | ||
| } | ||
|
|
||
| const currentBillingAccount = params.billingAccounts.find( |
There was a problem hiding this comment.
[correctness]
Consider using Array.prototype.find with a type guard or a more specific type assertion to ensure currentBillingAccount is of type BillingAccount. This will help prevent runtime errors if billingAccounts contains unexpected types.
| const currentBillingAccount = params.billingAccounts.find( | ||
| billingAccount => String(billingAccount.id) === params.currentBillingAccountId, | ||
| ) | ||
| const billingAccountWithDetails: ProjectBillingAccount | BillingAccount | undefined |
There was a problem hiding this comment.
[design]
The variable billingAccountWithDetails is assigned using a fallback pattern. Ensure that params.projectBillingAccount and currentBillingAccount are mutually exclusive or handle cases where both might be defined to avoid unexpected behavior.
| } | ||
|
|
||
| return { | ||
| endDate: getBillingAccountDate(billingAccountWithDetails, 'endDate'), |
There was a problem hiding this comment.
[correctness]
The function getBillingAccountDate is called with billingAccountWithDetails which can be either ProjectBillingAccount or BillingAccount. Ensure that both types have the expected properties (endDate and startDate) to avoid potential runtime errors.
| return { | ||
| endDate: getBillingAccountDate(billingAccountWithDetails, 'endDate'), | ||
| id: params.currentBillingAccountId, | ||
| name: resolvedBillingAccountName || getBillingAccountName(billingAccountWithDetails), |
There was a problem hiding this comment.
[💡 maintainability]
The resolvedBillingAccountName is computed using multiple fallbacks. Consider logging or handling cases where all fallbacks result in undefined to improve debugging and ensure data integrity.
| const savedChallenge = await createChallenge({ | ||
| name: formData.name, | ||
| projectId: createProjectId, | ||
| status: CHALLENGE_STATUS.NEW, |
There was a problem hiding this comment.
[❗❗ correctness]
The removal of roundType from the createChallenge payload may impact the challenge creation logic if roundType is required or used elsewhere in the application. Ensure that this change is intentional and that roundType is not needed for the challenge creation process.
| } | ||
|
|
||
| downloadProfileAsync(application.handle) | ||
| .catch(() => undefined) |
There was a problem hiding this comment.
[maintainability]
Catching and ignoring all errors with .catch(() => undefined) can make debugging difficult. Consider logging the error or handling specific error cases.
| </div> | ||
| <div> | ||
| <span className={styles.label}>Experience (years)</span> | ||
| <span className={styles.value}>{application.yearsOfExperience ?? '-'}</span> |
There was a problem hiding this comment.
[correctness]
Using the nullish coalescing operator (??) is appropriate here, but ensure that application.yearsOfExperience is not intended to be 0, as 0 will be replaced by '-'. If 0 is a valid value, consider using a different check.
| ): Resource[] { | ||
| const submitterRoleId = getSubmitterRoleId(resourceRoles) | ||
|
|
||
| return resources.filter(resource => { |
There was a problem hiding this comment.
[💡 performance]
The normalizeValue function is called twice for resource.role and resource.roleName. Consider normalizing once and storing the result to improve performance slightly.
| ): string { | ||
| const assignedMemberLabel = assignedTaskMember.handle || `User Id: ${assignedTaskMember.userId}` | ||
|
|
||
| return `Are you sure want to complete task "${challengeName}" ` |
There was a problem hiding this comment.
[correctness]
The confirmation message uses String(taskPrizeAmount), which will convert undefined to the string 'undefined'. Consider handling the case where taskPrizeAmount is undefined to avoid displaying an incorrect message.
| gap: 16px; | ||
| } | ||
|
|
||
| .launchButton { |
There was a problem hiding this comment.
[💡 maintainability]
Consider using CSS variables with more descriptive names instead of --btn-variant, --btn-variant--hover, and --btn-variant--active to improve maintainability and readability. This will make it clearer what these variables are intended for, especially if they are used in multiple places.
| return undefined | ||
| } | ||
|
|
||
| onSavingChange(isSaving) |
There was a problem hiding this comment.
[correctness]
The onSavingChange callback is called with isSaving in the useEffect hook. Ensure that isSaving is correctly initialized and updated to prevent incorrect state propagation.
| setSaveStatus('saved') | ||
|
|
||
| reset(nextValues) | ||
| onChallengeStatusChange?.(normalizeStatus(nextValues.status)) |
There was a problem hiding this comment.
[correctness]
The optional chaining operator ?. is used with onChallengeStatusChange. Ensure that this function is always defined when needed, as the silent failure could lead to missed updates.
| className={styles.launchButton} | ||
| disabled={props.isLaunchDisabled} | ||
| label={props.launchButtonLabel || 'Launch'} | ||
| onClick={onLaunchOpen} |
There was a problem hiding this comment.
[💡 design]
The onClick handler for the launch button directly uses onLaunchOpen. Consider wrapping this in a function to handle any potential side effects or errors that may occur during execution.
| }: UseFetchChallengeTypesResult = useFetchChallengeTypes() | ||
|
|
||
| const options = useMemo<FormSelectOption[]>( | ||
| () => buildChallengeTypeOptions(challengeTypes), |
There was a problem hiding this comment.
[❗❗ correctness]
Ensure that buildChallengeTypeOptions handles the filtering and sorting logic correctly, as it replaces the previous inline logic. Verify that it maintains the same behavior, especially regarding filtering active types and sorting by name.
| isTopgearTaskChallengeType, | ||
| } from './ChallengeTypeField.utils' | ||
|
|
||
| function buildChallengeType(overrides: Partial<ChallengeType>): ChallengeType { |
There was a problem hiding this comment.
[correctness]
Consider adding validation for the overrides object to ensure that it only contains valid properties of ChallengeType. This can prevent potential issues if incorrect properties are passed.
| }) | ||
|
|
||
| describe('buildChallengeTypeOptions', () => { | ||
| it('keeps only active launchable challenge types and sorts them by label', () => { |
There was a problem hiding this comment.
[correctness]
The test description mentions sorting by label, but the test does not explicitly verify the sorting order. Consider adding assertions to check the order of the options to ensure the sorting logic is correctly implemented.
| .trim() | ||
| .toUpperCase() | ||
| const normalizedName = (challengeType.name || '') | ||
| .replaceAll(/\s+/g, '') |
There was a problem hiding this comment.
[maintainability]
Consider using replace instead of replaceAll for better compatibility with older JavaScript environments. replaceAll is not supported in all environments, whereas replace can achieve the same result with a global regex.
| SUBMITTER_RESOURCE_ROLE_NAMES, | ||
| 'memberId', | ||
| resourcesOverride, | ||
| resourceRolesOverride, |
There was a problem hiding this comment.
[maintainability]
The getPersistedAssignmentValue function is called twice with similar parameters in the isTaskSingleAssignmentChallenge function. Consider refactoring to avoid duplicate logic and improve maintainability.
| ]) | ||
|
|
||
| useEffect(() => { | ||
| let isActive = true |
There was a problem hiding this comment.
[correctness]
The isActive flag is used to prevent state updates on unmounted components, which is a good practice. However, consider using the useRef hook for isActive to avoid potential issues with stale closures in asynchronous operations.
|
|
||
| const canManage = contextValue.isAdmin || contextValue.isManager | ||
| const canCreateEngagement = contextValue.isAdmin | ||
| || contextValue.userRoles.some(role => TALENT_MANAGER_ROLES.includes(role)) |
There was a problem hiding this comment.
[💡 performance]
The use of some with includes to check roles is correct, but ensure TALENT_MANAGER_ROLES is a small array to avoid performance issues. If the array is large, consider optimizing this check.
| </Link> | ||
| )} | ||
|
|
||
| {canCreateEngagement && ( |
There was a problem hiding this comment.
[💡 readability]
The nested ternary operator for rendering the 'Create Engagement' button can be difficult to read. Consider refactoring to improve readability, such as using a separate function or clearer conditional logic.
| interface ProjectBillingAccountExpiredNoticeProps { | ||
| billingAccountId?: number | string | ||
| billingAccountName?: string | ||
| canManageProject: boolean |
There was a problem hiding this comment.
[correctness]
The canManageProject prop is added to the ProjectBillingAccountExpiredNoticeProps interface but is not marked as optional. Ensure that all components using this interface provide this prop, or consider making it optional if it's not always required.
| } | ||
|
|
||
| export const ProjectsTable: FC<ProjectsTableProps> = (props: ProjectsTableProps) => { | ||
| const canEditProject = props.canEditProject || NOOP_CAN_EDIT_PROJECT |
There was a problem hiding this comment.
[design]
The NOOP_CAN_EDIT_PROJECT function is used as a default for canEditProject. Consider documenting or ensuring that this behavior is intentional, as it may lead to unexpected results if the canEditProject prop is not provided.
| type: 'action', | ||
| }, | ||
| ], | ||
| [billingAccountNames, canEditProject], |
There was a problem hiding this comment.
[performance]
The dependency array for useMemo includes canEditProject, which is a function. Functions can change identity on each render, potentially causing unnecessary re-renders. Consider using a stable reference or memoizing the function if possible.
| export const MANAGER_ROLES = [ | ||
| 'project manager', | ||
| 'topcoder project manager', | ||
| ...TALENT_MANAGER_ROLES, |
There was a problem hiding this comment.
[💡 maintainability]
Using the spread operator to include TALENT_MANAGER_ROLES in MANAGER_ROLES is a good approach for maintainability. However, ensure that the order of roles in MANAGER_ROLES is intentional and does not affect any logic that depends on the order of roles.
| export const TASK_MANAGER_ROLES = [ | ||
| 'topcoder task manager', | ||
| 'task manager', | ||
| ...TALENT_MANAGER_ROLES, |
There was a problem hiding this comment.
[💡 maintainability]
Including TALENT_MANAGER_ROLES in TASK_MANAGER_ROLES using the spread operator is efficient. Verify that this change does not unintentionally alter any existing logic or permissions that rely on the specific roles within TASK_MANAGER_ROLES.
| const members = Array.isArray(project.members) | ||
| ? project.members | ||
| .map(member => normalizeProjectMember(member)) | ||
| .filter((member): member is ProjectMember => !!member) |
There was a problem hiding this comment.
[correctness]
The filter function filter((member): member is ProjectMember => !!member) is used to ensure that only truthy values are kept. However, this assumes that normalizeProjectMember will never return a falsy value that is still a valid ProjectMember. Consider explicitly checking for the required properties of ProjectMember to ensure correctness.
|
|
||
| jest.mock('~/config', () => ({ | ||
| EnvironmentConfig: new Proxy({}, { | ||
| get: (): string => 'https://www.topcoder-dev.com', |
There was a problem hiding this comment.
[💡 maintainability]
Using a Proxy to return a constant URL string for EnvironmentConfig might be over-engineered. Consider using a simple object or constant instead, unless dynamic behavior is planned for future use.
| } | ||
|
|
||
| afterEach(() => { | ||
| mockedDecodeToken.mockReset() |
There was a problem hiding this comment.
[maintainability]
Consider using jest.clearAllMocks() in afterEach to ensure all mocked functions are reset, not just mockedDecodeToken. This can prevent potential test interference if more mocks are added in the future.
| return true | ||
| } | ||
|
|
||
| const normalizedRole = normalizeValue(getProjectMemberByUserId(project, userId)?.role) |
There was a problem hiding this comment.
[correctness]
The use of normalizeValue on getProjectMemberByUserId(project, userId)?.role may lead to unexpected behavior if role is undefined or not a string. Consider handling these cases explicitly to avoid potential issues.
| ): ProjectInvite | undefined { | ||
| const tokenData = getTokenData(token) | ||
| const normalizedUserId = normalizeUserId(tokenData.userId) | ||
| const invites = Array.isArray(project.invites) |
There was a problem hiding this comment.
[correctness]
The normalizeUserId function is used to normalize tokenData.userId, which could be undefined. Ensure that normalizeUserId can handle undefined values gracefully to prevent potential issues.
| const projectResult = useFetchProject(projectId || undefined) | ||
| const projectMembersResult = useFetchProjectMembers(projectId || undefined) | ||
| const attachmentsResult = useFetchProjectAttachments(projectId || undefined) | ||
| const canManageProject = !!projectResult.project |
There was a problem hiding this comment.
[💡 readability]
The canManageProject variable is initialized using a double negation (!!) to convert projectResult.project to a boolean. This is unnecessary since checkCanManageProject already returns a boolean. Consider simplifying this expression to improve readability.
| <ProjectBillingAccountExpiredNotice | ||
| billingAccountId={projectResult.project?.billingAccountId} | ||
| billingAccountName={projectResult.project?.billingAccountName} | ||
| canManageProject={canManageProject} |
There was a problem hiding this comment.
[correctness]
The canManageProject prop is added to ProjectBillingAccountExpiredNotice. Ensure that this component properly handles this new prop and that it does not introduce any unintended side effects.
| const titleAction = projectId | ||
| ? ( | ||
| <div className={styles.projectTitleActions}> | ||
| {canManageProject |
There was a problem hiding this comment.
[💡 maintainability]
The conditional rendering of the 'Edit project' link based on canManageProject could lead to an empty div if the condition is false. Consider providing a fallback or restructuring to avoid rendering unnecessary elements.
| const pageTitle = projectIdFromRoute && projectResult.project?.name | ||
| ? projectResult.project.name | ||
| : 'Challenges' | ||
| const canManageProject = !!projectResult.project |
There was a problem hiding this comment.
[❗❗ correctness]
The canManageProject variable is being calculated using the checkCanManageProject function. Ensure that this function correctly handles all edge cases, such as when userRoles or loginUserInfo are undefined, to prevent potential runtime errors.
| ? projectResult.project.name | ||
| : 'Challenges' | ||
| const canManageProject = !!projectResult.project | ||
| && checkCanManageProject(userRoles, loginUserInfo?.userId, projectResult.project) |
There was a problem hiding this comment.
[maintainability]
The checkCanManageProject function is used to determine if a user can manage a project. Consider adding unit tests for this function to ensure it behaves correctly across different scenarios, especially when user roles or project details change.
| : undefined | ||
|
|
||
| const titleAction = renderProjectTitleAction({ | ||
| backTo: `${location.pathname}${location.search}${location.hash}`, |
There was a problem hiding this comment.
[❗❗ security]
The backTo parameter is constructed using location.pathname, location.search, and location.hash. Ensure that these values are correctly sanitized to prevent potential security issues such as open redirects or XSS vulnerabilities.
| ? `${projectResult.project.name} Engagements` | ||
| : 'Engagements' | ||
| ) | ||
| const canManageProject = !!projectResult.project |
There was a problem hiding this comment.
[❗❗ correctness]
The canManageProject variable is calculated using a non-null assertion on projectResult.project. Ensure that projectResult.project is always defined when this code executes, or handle the case where it might be undefined to prevent potential runtime errors.
| const titleAction = projectId | ||
| ? ( | ||
| <div className={styles.projectTitleActions}> | ||
| {canManageProject |
There was a problem hiding this comment.
[correctness]
The conditional rendering of the 'Edit project' link using canManageProject is correct, but consider handling the case where projectId might be undefined to prevent potential issues with the to prop in the Link component.
| <ProjectBillingAccountExpiredNotice | ||
| billingAccountId={projectResult.project?.billingAccountId} | ||
| billingAccountName={projectResult.project?.billingAccountName} | ||
| canManageProject={canManageProject} |
There was a problem hiding this comment.
[💡 design]
Passing canManageProject to ProjectBillingAccountExpiredNotice is a good practice for conditional rendering. Ensure that the component properly handles the canManageProject prop to avoid rendering issues.
| const projectResult = useFetchProject(projectId) | ||
| const projectTypesResult = useFetchProjectTypes() | ||
|
|
||
| const canCreateProject = checkCanManageProject(userRoles, loginUserInfo?.userId) |
There was a problem hiding this comment.
[❗❗ correctness]
The function checkCanManageProject is used to determine both canCreateProject and canManageProject, but the parameters passed to it differ. Ensure that checkCanManageProject is designed to handle these different contexts correctly, as it might lead to incorrect permission checks if not properly implemented.
| const [sortBy, setSortBy] = useState<string>('lastActivityAt') | ||
| const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc') | ||
|
|
||
| const canCreateProject = checkCanManageProject(userRoles, loginUserInfo?.userId) |
There was a problem hiding this comment.
[correctness]
The checkCanManageProject function is used to determine both canCreateProject and canEditProject. Ensure that this function correctly handles both scenarios, as the logic might differ for creating versus editing a project.
|
|
||
| const canCreateProject = checkCanManageProject(userRoles, loginUserInfo?.userId) | ||
| const canEditProject = useCallback( | ||
| (project: Project): boolean => checkCanManageProject(userRoles, loginUserInfo?.userId, project), |
There was a problem hiding this comment.
[performance]
The useCallback hook is used to memoize canEditProject. Ensure that checkCanManageProject is a pure function, as any side effects could lead to unexpected behavior when the dependencies change.
| patchChallenge: jest.fn(), | ||
| })) | ||
| jest.mock('../../../../lib/utils', () => ({ | ||
| formatLastSaved: () => '', |
There was a problem hiding this comment.
[correctness]
The formatLastSaved function returns an empty string. Ensure this is intentional and that it doesn't affect the functionality where it's used.
| wiproAllowed: false, | ||
| workType: undefined, | ||
| }), | ||
| transformFormDataToChallenge: (formData: unknown) => formData, |
There was a problem hiding this comment.
[correctness]
The transformFormDataToChallenge function currently returns the input formData without any transformation. Verify if this is the intended behavior or if transformation logic is missing.
| fetchedResources, | ||
| fetchedResourceRoles, | ||
| ]) => { | ||
| if (!isActive || isFormDirtyRef.current) { |
There was a problem hiding this comment.
[correctness]
The check if (!isActive || isFormDirtyRef.current) could lead to unexpected behavior if the form becomes dirty after the initial reset but before the promise resolves. Consider handling this case to ensure the form state remains consistent.
| * @returns `true` when the caller can create engagements; otherwise `false`. | ||
| */ | ||
| export function canCreateEngagement(userRoles: string[]): boolean { | ||
| return hasAdminRole(userRoles) || checkTalentManager(userRoles) |
There was a problem hiding this comment.
[correctness]
The function canCreateEngagement uses checkTalentManager to determine if a user has a Talent Manager role. Ensure that checkTalentManager is correctly implemented to check against the TALENT_MANAGER_ROLES constant, as any discrepancy could lead to incorrect permission checks.
| primary: true, | ||
| })} | ||
|
|
||
| {params.canCreateEngagement |
There was a problem hiding this comment.
[💡 readability]
The conditional rendering of renderCreateActionButton using a ternary operator is correct, but using undefined as the fallback can be less clear. Consider using null instead, which is more idiomatic in React for rendering nothing.
| .trim() | ||
| .toLowerCase() === PROJECT_STATUS.ACTIVE | ||
| const isCreateActionDisabled = !!projectIdFromRoute && !isProjectActive | ||
| const canCreateProjectEngagement = canCreateEngagement(userRoles) |
There was a problem hiding this comment.
[correctness]
The variable isCreateActionDisabled is determined by checking if projectIdFromRoute is truthy and isProjectActive is false. Ensure that this logic correctly reflects the intended conditions under which the create action should be disabled, as any change in these conditions could lead to incorrect behavior.
| ProjectListTabs, | ||
| } from '../../../lib/components' | ||
| import { | ||
| canCreateEngagement, |
There was a problem hiding this comment.
[💡 maintainability]
The function canCreateEngagement is imported but not used directly in the code. Ensure that the function is necessary for the current implementation, or remove the import to avoid dead code.
| const contextValue = workAppContext as WorkAppContextModel | ||
|
|
||
| const canManage = contextValue.isAdmin || contextValue.isManager | ||
| const canCreateProjectEngagement = canCreateEngagement(contextValue.userRoles) |
There was a problem hiding this comment.
[correctness]
The variable canCreateProjectEngagement is derived from canCreateEngagement(contextValue.userRoles). Ensure that canCreateEngagement correctly handles all possible values of contextValue.userRoles to avoid unexpected behavior.
https://work.topcoder-dev.com