From 162c825d807c409bab8a8c7671af41b695b93e0a Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 11:27:23 -0800 Subject: [PATCH 1/9] TCA-852 #comment This commit prevents wipro users w/out dice enabled from access any TCA lesson. #time 1h --- .../token-functions/token.functions.ts | 6 ++-- .../functions/token-functions/token.model.ts | 1 + src-ts/lib/functions/user-functions/index.ts | 5 +++- .../user-functions/user-store/index.ts | 5 +++- .../user-store/user-xhr.store.ts | 17 ++++++++++- .../user-functions/user.functions.ts | 8 +++-- src-ts/lib/page-footer/PageFooter.tsx | 18 ++++++----- .../profile-factory/profile.factory.ts | 14 ++++++--- .../profile-functions/profile.functions.ts | 14 +++++---- .../profile-provider/user-profile.model.ts | 2 ++ .../course-curriculum/CourseCurriculum.tsx | 17 +++++++++-- .../learn/free-code-camp/FreeCodeCamp.tsx | 30 +++++++++++++++---- 12 files changed, 106 insertions(+), 31 deletions(-) diff --git a/src-ts/lib/functions/token-functions/token.functions.ts b/src-ts/lib/functions/token-functions/token.functions.ts index 32b523196..3e8059fe9 100644 --- a/src-ts/lib/functions/token-functions/token.functions.ts +++ b/src-ts/lib/functions/token-functions/token.functions.ts @@ -15,9 +15,10 @@ export async function getAsync(): Promise { } try { - const { handle, roles }: { + const { handle, roles, userId }: { handle?: string roles?: Array + userId?: number } = decodeToken(token) // if we didn't find the handle, we have a bad token @@ -26,8 +27,9 @@ export async function getAsync(): Promise { return Promise.resolve({}) } - return Promise.resolve({ handle, roles, token }) + return Promise.resolve({ handle, roles, token, userId }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any } catch (error: any) { logError(error) return Promise.resolve({}) diff --git a/src-ts/lib/functions/token-functions/token.model.ts b/src-ts/lib/functions/token-functions/token.model.ts index 9a5c03bec..dd4b8e1b9 100644 --- a/src-ts/lib/functions/token-functions/token.model.ts +++ b/src-ts/lib/functions/token-functions/token.model.ts @@ -2,4 +2,5 @@ export interface TokenModel { handle?: string roles?: Array token?: string + userId?: number } diff --git a/src-ts/lib/functions/user-functions/index.ts b/src-ts/lib/functions/user-functions/index.ts index 617ec8e4a..7a5ad1a47 100644 --- a/src-ts/lib/functions/user-functions/index.ts +++ b/src-ts/lib/functions/user-functions/index.ts @@ -1 +1,4 @@ -export { updatePasswordAsync as userUpdatePasswordAsync } from './user.functions' +export { + getDiceStatusAsync as userGetDiceStatusAsync, + updatePasswordAsync as userUpdatePasswordAsync, +} from './user.functions' 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 fa880fb95..368784135 100644 --- a/src-ts/lib/functions/user-functions/user-store/index.ts +++ b/src-ts/lib/functions/user-functions/user-store/index.ts @@ -1,2 +1,5 @@ -export { patchAsync as userPatchAsync } from './user-xhr.store' +export { + getDiceStatusAsync as userStoreGetDiceStatusAsync, + 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 789e41458..faf3951b2 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 @@ -1,5 +1,5 @@ import { User } from '../../../../../types/tc-auth-lib' -import { xhrPatchAsync } from '../../xhr-functions' +import { xhrGetAsync, xhrPatchAsync } from '../../xhr-functions' import { user as userEndpoint } from './user-endpoint.config' @@ -12,6 +12,21 @@ 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 patchAsync(userId: number, request: UserPatchRequest): Promise { const url: string = userEndpoint(userId) return xhrPatchAsync(url, request) diff --git a/src-ts/lib/functions/user-functions/user.functions.ts b/src-ts/lib/functions/user-functions/user.functions.ts index 792d33791..d240c8d5b 100644 --- a/src-ts/lib/functions/user-functions/user.functions.ts +++ b/src-ts/lib/functions/user-functions/user.functions.ts @@ -1,4 +1,8 @@ -import { userPatchAsync, UserPatchRequest } from './user-store' +import { UserPatchRequest, userStoreGetDiceStatusAsync, userStorePatchAsync } from './user-store' + +export async function getDiceStatusAsync(userId: number): Promise { + return userStoreGetDiceStatusAsync(userId) +} export async function updatePasswordAsync(userId: number, currentPassword: string, password: string): Promise { const request: UserPatchRequest = { @@ -9,6 +13,6 @@ export async function updatePasswordAsync(userId: number, currentPassword: strin }, }, } - return userPatchAsync(userId, request) + return userStorePatchAsync(userId, request) .then(() => undefined) } diff --git a/src-ts/lib/page-footer/PageFooter.tsx b/src-ts/lib/page-footer/PageFooter.tsx index 1762265a8..9b3bdfbfa 100644 --- a/src-ts/lib/page-footer/PageFooter.tsx +++ b/src-ts/lib/page-footer/PageFooter.tsx @@ -7,13 +7,17 @@ const PageFooter: FC<{}> = () => { const navElementId: string = 'footer-nav-el' - tcUniNav( - 'init', - navElementId, - { - type: 'footer', - }, - ) + // delay the initialization so + // the nav element has time to render + setTimeout(() => { + tcUniNav( + 'init', + navElementId, + { + type: 'footer', + }, + ) + }, 10) return
} diff --git a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts index 7e5fb0aff..ff2f565c3 100644 --- a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts +++ b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts @@ -3,18 +3,24 @@ import { UserProfile } from '../../user-profile.model' import { UserRole } from './user-role.enum' -export function create(profile: UserProfile, token: TokenModel): UserProfile { - // TODO: create the profile full name property +export function create(profile: UserProfile, token: TokenModel, hasMfaEnabled: boolean): UserProfile { + // Currently, the "Self-Service Customer" role is being set when a user is created // during the self-service workflow. There are no other roles being set to distinguish // between Customers and Members. // Therefore, the only way to know if a user is a Member is if s/he is not a Customer. // This is imperfect, bc a user could be both a Customer or a Member, but for now // we are okay with this and will have a more in-depth initiave to properly assign - // rolees. + // roles. profile.isCustomer = !!token.roles?.some(role => role === UserRole.customer) profile.isMember = !profile.isCustomer + + profile.isWipro = profile.email.endsWith('@topcoder.com') + profile.diceEnabled = hasMfaEnabled + // store roles for custom capability checks - profile.roles = token.roles + profile.roles = token.roles || [] + + // TODO: create the profile full name property return profile } diff --git a/src-ts/lib/profile-provider/profile-functions/profile.functions.ts b/src-ts/lib/profile-provider/profile-functions/profile.functions.ts index 2499043a6..d73404bdf 100644 --- a/src-ts/lib/profile-provider/profile-functions/profile.functions.ts +++ b/src-ts/lib/profile-provider/profile-functions/profile.functions.ts @@ -1,3 +1,4 @@ +import { userGetDiceStatusAsync } from '../../functions/user-functions' import { tokenGetAsync, TokenModel } from '../../functions/token-functions' import { EditNameRequest } from '../edit-name-request.model' import { UserProfile } from '../user-profile.model' @@ -11,19 +12,22 @@ export async function getAsync(handle?: string): Promise = profileStoreGet(safeHandle) + const mfaPromise: Promise = userGetDiceStatusAsync(token.userId) + + const [profileResult, mfaEnabled]: [UserProfile, boolean] = await Promise.all([profilePromise, mfaPromise]) // make the changes we need based on the token - const output: UserProfile = profileFactoryCreate(profileResult, token) + const output: UserProfile = profileFactoryCreate(profileResult, token, mfaEnabled) return output } -export async function editNameAsync(handle: string, profile: EditNameRequest): Promise { +export async function editNameAsync(handle: string, profile: EditNameRequest): Promise { return profileStorePatchName(handle, profile) } diff --git a/src-ts/lib/profile-provider/user-profile.model.ts b/src-ts/lib/profile-provider/user-profile.model.ts index 51ae0c2e1..d557c5e22 100644 --- a/src-ts/lib/profile-provider/user-profile.model.ts +++ b/src-ts/lib/profile-provider/user-profile.model.ts @@ -1,6 +1,7 @@ export interface UserProfile { competitionCountryCode: string createdAt: number + diceEnabled: boolean email: string firstName: string handle: string @@ -8,6 +9,7 @@ export interface UserProfile { homeCountryCode: string isCustomer?: boolean isMember?: boolean + isWipro: boolean lastName: string photoURL?: string roles: Array 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 408c65466..51637a968 100644 --- a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx +++ b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx @@ -37,11 +37,12 @@ interface CourseCurriculumProps { const CourseCurriculum: FC = (props: CourseCurriculumProps) => { const navigate: NavigateFunction = useNavigate() - const [searchParams]: any = useSearchParams() + const [searchParams]: [URLSearchParams, unknown] = useSearchParams() const isLoggedIn: boolean = !!props.profile - const [isTcAcademyPolicyModal, setIsTcAcademyPolicyModal]: [boolean, Dispatch>] = useState(false) + const [isTcAcademyPolicyModal, setIsTcAcademyPolicyModal]: [boolean, Dispatch>] + = useState(false) const status: string = props.progress?.status ?? UserCertificationProgressStatus.inititialized const completedPercentage: number = (props.progress?.courseProgressPercentage ?? 0) / 100 @@ -76,6 +77,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp * Handle user click on start course/resume/login button */ const handleStartCourseClick: () => void = useCallback(() => { + console.debug(props.profile) // if user is not logged in, redirect to login page if (!isLoggedIn) { // add a flag to the return url to show the academic policy modal @@ -84,6 +86,14 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp return } + // if the user is wipro and s/he hasn't set up DICE, + // let them know + if (props.profile?.isWipro && !props.profile.diceEnabled) { + // TODO + console.debug('user needs dice') + return + } + // Check if user accepted policy and resume(or start) the course if (props.progress?.academicHonestyPolicyAcceptedAt) { handleStartCourse() @@ -92,6 +102,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 }, [ handleStartCourse, isLoggedIn, @@ -130,6 +141,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp } handleStartCourse() + // eslint-disable-next-line react-hooks/exhaustive-deps }, [ handleStartCourse, props.course.certificationId, @@ -149,6 +161,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp * proceed as if the user just clicked "Start course" button */ useEffect(() => { + // eslint-disable-next-line no-null/no-null if (props.progressReady && isLoggedIn && searchParams.get(LEARN_PATHS.startCourseRouteFlag) !== null) { handleStartCourseClick() } diff --git a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx index c76e6e532..725bfe76a 100644 --- a/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx +++ b/src-ts/tools/learn/free-code-camp/FreeCodeCamp.tsx @@ -436,19 +436,37 @@ const FreeCodeCamp: FC<{}> = () => { /** * Check if the user accepted the academic honesty policy + * and either is not a wipro user or the wipro user has dice enabled. * if not, redirect user to course details page to accept the policy */ useLayoutEffect(() => { - if (ready && !(isLoggedIn && certificateProgress?.academicHonestyPolicyAcceptedAt)) { - const coursePath: string = getCoursePath( - providerParam, - certificationParam, - ) - navigate(coursePath) + + // if we're not ready, there's nothing to do + if (!ready) { + return } + + // if the user is logged in, + // and the user is a either not wipro user or is a wipro user with dice enabled, + // and if the user has accepted the academic honesty policy, + // the user is permitted to take the course, so there's nothing to do. + if (isLoggedIn + && (!profile?.isWipro || !!profile?.diceEnabled) + && !!certificateProgress?.academicHonestyPolicyAcceptedAt) { + return + } + + // redirect the user to course details page to perform the + // necessary actions + const coursePath: string = getCoursePath( + providerParam, + certificationParam, + ) + navigate(coursePath) }, [ ready, certificateProgress, + profile, providerParam, certificationParam, navigate, From 91e0bdff6c460e5f566b4b2253e201e14894c229 Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 11:27:43 -0800 Subject: [PATCH 2/9] TCA-852 Change domain back to wipro --- .../profile-functions/profile-factory/profile.factory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts index ff2f565c3..6567dafae 100644 --- a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts +++ b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts @@ -15,7 +15,7 @@ export function create(profile: UserProfile, token: TokenModel, hasMfaEnabled: b profile.isCustomer = !!token.roles?.some(role => role === UserRole.customer) profile.isMember = !profile.isCustomer - profile.isWipro = profile.email.endsWith('@topcoder.com') + profile.isWipro = profile.email.endsWith('@wipro.com') profile.diceEnabled = hasMfaEnabled // store roles for custom capability checks From 5f71fc51d8c2fd59671e9f28cd50a900fbb82f9b Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 11:38:34 -0800 Subject: [PATCH 3/9] TCA-852 Fix argument name --- .../profile-functions/profile-factory/profile.factory.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts index 6567dafae..13fe53fdd 100644 --- a/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts +++ b/src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts @@ -3,7 +3,7 @@ import { UserProfile } from '../../user-profile.model' import { UserRole } from './user-role.enum' -export function create(profile: UserProfile, token: TokenModel, hasMfaEnabled: boolean): UserProfile { +export function create(profile: UserProfile, token: TokenModel, hasDiceEnabled: boolean): UserProfile { // Currently, the "Self-Service Customer" role is being set when a user is created // during the self-service workflow. There are no other roles being set to distinguish @@ -16,7 +16,7 @@ export function create(profile: UserProfile, token: TokenModel, hasMfaEnabled: b profile.isMember = !profile.isCustomer profile.isWipro = profile.email.endsWith('@wipro.com') - profile.diceEnabled = hasMfaEnabled + profile.diceEnabled = hasDiceEnabled // store roles for custom capability checks profile.roles = token.roles || [] From 0a15807c4e929bb72e62a18ad81ec14cf7ace7a0 Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 11:40:10 -0800 Subject: [PATCH 4/9] TCA-852 fix property names --- .../profile-provider/profile-functions/profile.functions.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src-ts/lib/profile-provider/profile-functions/profile.functions.ts b/src-ts/lib/profile-provider/profile-functions/profile.functions.ts index d73404bdf..ede0f9ef7 100644 --- a/src-ts/lib/profile-provider/profile-functions/profile.functions.ts +++ b/src-ts/lib/profile-provider/profile-functions/profile.functions.ts @@ -19,12 +19,12 @@ export async function getAsync(handle?: string): Promise = profileStoreGet(safeHandle) - const mfaPromise: Promise = userGetDiceStatusAsync(token.userId) + const dicePromise: Promise = userGetDiceStatusAsync(token.userId) - const [profileResult, mfaEnabled]: [UserProfile, boolean] = await Promise.all([profilePromise, mfaPromise]) + const [profileResult, diceEnabled]: [UserProfile, boolean] = await Promise.all([profilePromise, dicePromise]) // make the changes we need based on the token - const output: UserProfile = profileFactoryCreate(profileResult, token, mfaEnabled) + const output: UserProfile = profileFactoryCreate(profileResult, token, diceEnabled) return output } From fe092c2c0247996c3647bdc5e218d0c2d185517d Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 11:41:01 -0800 Subject: [PATCH 5/9] TCA-852 remove console log --- .../course-details/course-curriculum/CourseCurriculum.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 51637a968..0dc1abd4e 100644 --- a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx +++ b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx @@ -77,7 +77,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp * Handle user click on start course/resume/login button */ const handleStartCourseClick: () => void = useCallback(() => { - console.debug(props.profile) + // if user is not logged in, redirect to login page if (!isLoggedIn) { // add a flag to the return url to show the academic policy modal @@ -90,7 +90,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp // let them know if (props.profile?.isWipro && !props.profile.diceEnabled) { // TODO - console.debug('user needs dice') + console.debug('TODO: user needs dice') return } From cbcfac493f74691bfbabe0bd64694fbca03eaea1 Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 11:41:45 -0800 Subject: [PATCH 6/9] TCA-852 fix grammar --- .../learn/course-details/course-curriculum/CourseCurriculum.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0dc1abd4e..ab1d97727 100644 --- a/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx +++ b/src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx @@ -87,7 +87,7 @@ const CourseCurriculum: FC = (props: CourseCurriculumProp } // if the user is wipro and s/he hasn't set up DICE, - // let them know + // let the user know if (props.profile?.isWipro && !props.profile.diceEnabled) { // TODO console.debug('TODO: user needs dice') From c9cb23b3379fa957772245432abfef3feeed7a8e Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 12:40:33 -0800 Subject: [PATCH 7/9] TCA-852 #comment This commit adds the modal to tell wipro users they need to set up mfa w/dice #time 1h --- src-ts/.eslintrc.js | 6 +++ .../environment.default.config.ts | 1 + .../environments/environment.prod.config.ts | 1 + .../user-store/user-xhr.store.ts | 3 +- src-ts/lib/global-config.model.ts | 1 + .../course-curriculum/CourseCurriculum.tsx | 25 +++++++-- .../dice-modal/DiceModal.module.scss | 13 +++++ .../dice-modal/DiceModal.tsx | 53 +++++++++++++++++++ .../course-curriculum/dice-modal/index.ts | 1 + 9 files changed, 98 insertions(+), 6 deletions(-) create mode 100644 src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.module.scss create mode 100644 src-ts/tools/learn/course-details/course-curriculum/dice-modal/DiceModal.tsx create mode 100644 src-ts/tools/learn/course-details/course-curriculum/dice-modal/index.ts 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/user-xhr.store.ts b/src-ts/lib/functions/user-functions/user-store/user-xhr.store.ts index faf3951b2..8cb4e1cc2 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 @@ -17,6 +17,7 @@ export async function getDiceStatusAsync(userId: number): Promise { interface DiceStatusResult { result: { content: { + mfaEnabled: boolean diceEnabled: boolean } } @@ -24,7 +25,7 @@ export async function getDiceStatusAsync(userId: number): Promise { const result: DiceStatusResult = await xhrGetAsync(`${userEndpoint(userId)}/2fa`) - return !!result.result.content.diceEnabled + return !!result.result.content.mfaEnabled && !!result.result.content.diceEnabled } export async function patchAsync(userId: number, request: UserPatchRequest): 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..7d967e793 --- /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 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' From 337220c81ea6574ceda73a99dff19666b29fceec Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 12:55:27 -0800 Subject: [PATCH 8/9] TCA-853 Clean up DICE store #time 15m --- .../user-functions/user-store/index.ts | 2 +- .../user-store/user-xhr.store.ts | 25 ++++++++----------- .../user-functions/user.functions.ts | 6 +++-- 3 files changed, 16 insertions(+), 17 deletions(-) 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 8cb4e1cc2..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,20 +21,8 @@ export interface UserPatchRequest { } } -export async function getDiceStatusAsync(userId: number): Promise { - - interface DiceStatusResult { - result: { - content: { - mfaEnabled: boolean - diceEnabled: boolean - } - } - } - const result: DiceStatusResult - = await xhrGetAsync(`${userEndpoint(userId)}/2fa`) - - return !!result.result.content.mfaEnabled && !!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 { From d08943dd51d59de3133bbdb6d04d4406ecd393ed Mon Sep 17 00:00:00 2001 From: Brooke Date: Fri, 23 Dec 2022 13:06:29 -0800 Subject: [PATCH 9/9] TCA-853 Improve copy --- .../course-curriculum/dice-modal/DiceModal.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 index 7d967e793..f580e189b 100644 --- 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 @@ -24,13 +24,13 @@ const DiceModal: FC = (props: DiceModalProps) => { onClose={props.onClose} open={isOpen} size='md' - title='DICE Multifactor Authentication Required' + title='DICE ID Multifactor Authentication Required' >

- Wipro requires employees to enable multifactor Authentication - with DICE in order to take Topcoder Academy courses. + 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.