diff --git a/src-ts/.eslintrc.js b/src-ts/.eslintrc.js index c54020a0f..10a2a1fb5 100644 --- a/src-ts/.eslintrc.js +++ b/src-ts/.eslintrc.js @@ -251,6 +251,12 @@ module.exports = { 2, 4, ], + 'react/jsx-no-bind': [ + 'error', + { + allowFunctions: true, + } + ], 'react/jsx-no-useless-fragment': [ 0 ], diff --git a/src-ts/config/environments/environment.default.config.ts b/src-ts/config/environments/environment.default.config.ts index c616a5f2d..525463eda 100644 --- a/src-ts/config/environments/environment.default.config.ts +++ b/src-ts/config/environments/environment.default.config.ts @@ -36,6 +36,7 @@ export const EnvironmentConfigDefault: EnvironmentConfigModel = { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.jl6Lp_friVNwEP8nfsfmL-vrQFzOFp2IfM_HC7AwGcg', }, TOPCODER_URLS: { + ACCOUNT_SETTINGS: `${COMMUNITY_WEBSITE}/settings/account`, API_BASE: `${COMMUNITY_WEBSITE}/api`, BLOG_PAGE: `${COMMUNITY_WEBSITE}/blog`, CHALLENGES_PAGE: `${COMMUNITY_WEBSITE}/challenges`, diff --git a/src-ts/config/environments/environment.prod.config.ts b/src-ts/config/environments/environment.prod.config.ts index da9a2b5e9..222edbb03 100644 --- a/src-ts/config/environments/environment.prod.config.ts +++ b/src-ts/config/environments/environment.prod.config.ts @@ -34,6 +34,7 @@ export const EnvironmentConfigProd: EnvironmentConfigModel = { 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.jl6Lp_friVNwEP8nfsfmL-vrQFzOFp2IfM_HC7AwGcg', }, TOPCODER_URLS: { + ACCOUNT_SETTINGS: `${COMMUNITY_WEBSITE}/settings/account`, API_BASE: `${COMMUNITY_WEBSITE}/api`, BLOG_PAGE: `${COMMUNITY_WEBSITE}/blog`, CHALLENGES_PAGE: `${COMMUNITY_WEBSITE}/challenges`, diff --git a/src-ts/lib/functions/user-functions/user-store/index.ts b/src-ts/lib/functions/user-functions/user-store/index.ts index 368784135..5e88d6a17 100644 --- a/src-ts/lib/functions/user-functions/user-store/index.ts +++ b/src-ts/lib/functions/user-functions/user-store/index.ts @@ -1,5 +1,5 @@ export { - getDiceStatusAsync as userStoreGetDiceStatusAsync, + getMfaStatusAsync as userStoreGetMfaStatusAsync, patchAsync as userStorePatchAsync, } from './user-xhr.store' export { type UserPatchRequest } from './user-xhr.store' diff --git a/src-ts/lib/functions/user-functions/user-store/user-xhr.store.ts b/src-ts/lib/functions/user-functions/user-store/user-xhr.store.ts index faf3951b2..82a5bca8d 100644 --- a/src-ts/lib/functions/user-functions/user-store/user-xhr.store.ts +++ b/src-ts/lib/functions/user-functions/user-store/user-xhr.store.ts @@ -3,6 +3,15 @@ import { xhrGetAsync, xhrPatchAsync } from '../../xhr-functions' import { user as userEndpoint } from './user-endpoint.config' +export interface MfaStatusResult { + result: { + content: { + diceEnabled: boolean + mfaEnabled: boolean + } + } +} + export interface UserPatchRequest { param: { credential: { @@ -12,19 +21,8 @@ export interface UserPatchRequest { } } -export async function getDiceStatusAsync(userId: number): Promise { - - interface DiceStatusResult { - result: { - content: { - diceEnabled: boolean - } - } - } - const result: DiceStatusResult - = await xhrGetAsync(`${userEndpoint(userId)}/2fa`) - - return !!result.result.content.diceEnabled +export async function getMfaStatusAsync(userId: number): Promise { + return xhrGetAsync(`${userEndpoint(userId)}/2fa`) } export async function patchAsync(userId: number, request: UserPatchRequest): Promise { diff --git a/src-ts/lib/functions/user-functions/user.functions.ts b/src-ts/lib/functions/user-functions/user.functions.ts index d240c8d5b..613e7100c 100644 --- a/src-ts/lib/functions/user-functions/user.functions.ts +++ b/src-ts/lib/functions/user-functions/user.functions.ts @@ -1,7 +1,9 @@ -import { UserPatchRequest, userStoreGetDiceStatusAsync, userStorePatchAsync } from './user-store' +import { UserPatchRequest, userStoreGetMfaStatusAsync, userStorePatchAsync } from './user-store' +import { MfaStatusResult } from './user-store/user-xhr.store' export async function getDiceStatusAsync(userId: number): Promise { - return userStoreGetDiceStatusAsync(userId) + const result: MfaStatusResult = await userStoreGetMfaStatusAsync(userId) + return !!result.result.content.mfaEnabled && !!result.result.content.diceEnabled } export async function updatePasswordAsync(userId: number, currentPassword: string, password: string): Promise { diff --git a/src-ts/lib/global-config.model.ts b/src-ts/lib/global-config.model.ts index 911bc42ba..18820b175 100644 --- a/src-ts/lib/global-config.model.ts +++ b/src-ts/lib/global-config.model.ts @@ -29,6 +29,7 @@ export interface GlobalConfig { CUSTOMER_TOKEN: string } TOPCODER_URLS: { + ACCOUNT_SETTINGS: string API_BASE: string BLOG_PAGE: string CHALLENGES_PAGE: string diff --git a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx index ab1d97727..c6ee2a0a5 100644 --- a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx +++ b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx @@ -24,6 +24,7 @@ import { import { CurriculumSummary } from './curriculum-summary' import { TcAcademyPolicyModal } from './tc-academy-policy-modal' +import { DiceModal } from './dice-modal' import styles from './CourseCurriculum.module.scss' interface CourseCurriculumProps { @@ -43,6 +44,8 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp const [isTcAcademyPolicyModal, setIsTcAcademyPolicyModal]: [boolean, Dispatch>] = useState(false) + const [isDiceModalOpen, setIsDiceModalOpen]: [boolean, Dispatch>] + = useState(false) const status: string = props.progress?.status ?? UserCertificationProgressStatus.inititialized const completedPercentage: number = (props.progress?.courseProgressPercentage ?? 0) / 100 @@ -89,8 +92,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp // if the user is wipro and s/he hasn't set up DICE, // let the user know if (props.profile?.isWipro && !props.profile.diceEnabled) { - // TODO - console.debug('TODO: user needs dice') + setIsDiceModalOpen(true) return } @@ -102,7 +104,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp // show the academic policy modal before starting a new course setIsTcAcademyPolicyModal(true) - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ handleStartCourse, isLoggedIn, @@ -141,7 +143,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp } handleStartCourse() - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ handleStartCourse, props.course.certificationId, @@ -167,6 +169,14 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp } }, [handleStartCourseClick, isLoggedIn, props.progressReady, searchParams]) + function onAcademicHonestyModalClose(): void { + setIsTcAcademyPolicyModal(false) + } + + function onDiceModalClose(): void { + setIsDiceModalOpen(false) + } + return ( <>
@@ -211,9 +221,14 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp setIsTcAcademyPolicyModal(false)} + onClose={onAcademicHonestyModalClose} onConfirm={handlePolicyAccept} /> + + ) } diff --git a/src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.module.scss b/src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.module.scss new file mode 100644 index 000000000..df1b5b572 --- /dev/null +++ b/src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.module.scss @@ -0,0 +1,13 @@ +@import '../../../../../lib/styles/includes'; + +.diceModal { + + p { + margin-bottom: $space-lg; + + &.buttonContainer { + display: flex; + justify-content: center; + } + } +} diff --git a/src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.tsx b/src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.tsx new file mode 100644 index 000000000..f580e189b --- /dev/null +++ b/src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.tsx @@ -0,0 +1,53 @@ +import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' + +import { EnvironmentConfig } from '../../../../../config' +import { BaseModal, Button } from '../../../../../lib' + +import styles from './DiceModal.module.scss' + +interface DiceModalProps { + isOpen: boolean + onClose: () => void +} + +const DiceModal: FC = (props: DiceModalProps) => { + + const [isOpen, setIsOpen]: [boolean, Dispatch>] + = useState(false) + + useEffect(() => { + setIsOpen(props.isOpen) + }, [props.isOpen]) + + return ( + +
+ +

+ Wipro requires employees to enable Multifactor Authentication + with DICE ID in order to take Topcoder Academy courses. +

+

+ Please go to Account Settings to configure your account. +

+

+

+ +
+ ) +} + +export default DiceModal diff --git a/src-ts/tools/learn/course-details/course-curriculum/dice-modal/index.ts b/src-ts/tools/learn/course-details/course-curriculum/dice-modal/index.ts new file mode 100644 index 000000000..b74a55774 --- /dev/null +++ b/src-ts/tools/learn/course-details/course-curriculum/dice-modal/index.ts @@ -0,0 +1 @@ +export { default as DiceModal } from './DiceModal'