Skip to content

Commit 336b6da

Browse files
Merge pull request #459 from topcoder-platform/dev
2.6 Wipro DICE IDs Release - 2023-01-04 -> master
2 parents e4cb992 + 91a1f42 commit 336b6da

File tree

19 files changed

+209
-32
lines changed

19 files changed

+209
-32
lines changed

src-ts/.eslintrc.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,12 @@ module.exports = {
251251
2,
252252
4,
253253
],
254+
'react/jsx-no-bind': [
255+
'error',
256+
{
257+
allowFunctions: true,
258+
}
259+
],
254260
'react/jsx-no-useless-fragment': [
255261
0
256262
],

src-ts/config/environments/environment.default.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const EnvironmentConfigDefault: EnvironmentConfigModel = {
3636
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.jl6Lp_friVNwEP8nfsfmL-vrQFzOFp2IfM_HC7AwGcg',
3737
},
3838
TOPCODER_URLS: {
39+
ACCOUNT_SETTINGS: `${COMMUNITY_WEBSITE}/settings/account`,
3940
API_BASE: `${COMMUNITY_WEBSITE}/api`,
4041
BLOG_PAGE: `${COMMUNITY_WEBSITE}/blog`,
4142
CHALLENGES_PAGE: `${COMMUNITY_WEBSITE}/challenges`,

src-ts/config/environments/environment.prod.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const EnvironmentConfigProd: EnvironmentConfigModel = {
3434
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.jl6Lp_friVNwEP8nfsfmL-vrQFzOFp2IfM_HC7AwGcg',
3535
},
3636
TOPCODER_URLS: {
37+
ACCOUNT_SETTINGS: `${COMMUNITY_WEBSITE}/settings/account`,
3738
API_BASE: `${COMMUNITY_WEBSITE}/api`,
3839
BLOG_PAGE: `${COMMUNITY_WEBSITE}/blog`,
3940
CHALLENGES_PAGE: `${COMMUNITY_WEBSITE}/challenges`,

src-ts/lib/functions/token-functions/token.functions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ export async function getAsync(): Promise<TokenModel> {
1515
}
1616

1717
try {
18-
const { handle, roles }: {
18+
const { handle, roles, userId }: {
1919
handle?: string
2020
roles?: Array<string>
21+
userId?: number
2122
} = decodeToken(token)
2223

2324
// if we didn't find the handle, we have a bad token
@@ -26,8 +27,9 @@ export async function getAsync(): Promise<TokenModel> {
2627
return Promise.resolve({})
2728
}
2829

29-
return Promise.resolve({ handle, roles, token })
30+
return Promise.resolve({ handle, roles, token, userId })
3031

32+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
3133
} catch (error: any) {
3234
logError(error)
3335
return Promise.resolve({})

src-ts/lib/functions/token-functions/token.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ export interface TokenModel {
22
handle?: string
33
roles?: Array<string>
44
token?: string
5+
userId?: number
56
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,4 @@
1-
export { updatePasswordAsync as userUpdatePasswordAsync } from './user.functions'
1+
export {
2+
getDiceStatusAsync as userGetDiceStatusAsync,
3+
updatePasswordAsync as userUpdatePasswordAsync,
4+
} from './user.functions'
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
1-
export { patchAsync as userPatchAsync } from './user-xhr.store'
1+
export {
2+
getMfaStatusAsync as userStoreGetMfaStatusAsync,
3+
patchAsync as userStorePatchAsync,
4+
} from './user-xhr.store'
25
export { type UserPatchRequest } from './user-xhr.store'

src-ts/lib/functions/user-functions/user-store/user-xhr.store.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,17 @@
11
import { User } from '../../../../../types/tc-auth-lib'
2-
import { xhrPatchAsync } from '../../xhr-functions'
2+
import { xhrGetAsync, xhrPatchAsync } from '../../xhr-functions'
33

44
import { user as userEndpoint } from './user-endpoint.config'
55

6+
export interface MfaStatusResult {
7+
result: {
8+
content: {
9+
diceEnabled: boolean
10+
mfaEnabled: boolean
11+
}
12+
}
13+
}
14+
615
export interface UserPatchRequest {
716
param: {
817
credential: {
@@ -12,6 +21,10 @@ export interface UserPatchRequest {
1221
}
1322
}
1423

24+
export async function getMfaStatusAsync(userId: number): Promise<MfaStatusResult> {
25+
return xhrGetAsync<MfaStatusResult>(`${userEndpoint(userId)}/2fa`)
26+
}
27+
1528
export async function patchAsync(userId: number, request: UserPatchRequest): Promise<User> {
1629
const url: string = userEndpoint(userId)
1730
return xhrPatchAsync(url, request)

src-ts/lib/functions/user-functions/user.functions.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { userPatchAsync, UserPatchRequest } from './user-store'
1+
import { UserPatchRequest, userStoreGetMfaStatusAsync, userStorePatchAsync } from './user-store'
2+
import { MfaStatusResult } from './user-store/user-xhr.store'
3+
4+
export async function getDiceStatusAsync(userId: number): Promise<boolean> {
5+
const result: MfaStatusResult = await userStoreGetMfaStatusAsync(userId)
6+
return !!result.result.content.mfaEnabled && !!result.result.content.diceEnabled
7+
}
28

39
export async function updatePasswordAsync(userId: number, currentPassword: string, password: string): Promise<void> {
410
const request: UserPatchRequest = {
@@ -9,6 +15,6 @@ export async function updatePasswordAsync(userId: number, currentPassword: strin
915
},
1016
},
1117
}
12-
return userPatchAsync(userId, request)
18+
return userStorePatchAsync(userId, request)
1319
.then(() => undefined)
1420
}

src-ts/lib/global-config.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ export interface GlobalConfig {
2929
CUSTOMER_TOKEN: string
3030
}
3131
TOPCODER_URLS: {
32+
ACCOUNT_SETTINGS: string
3233
API_BASE: string
3334
BLOG_PAGE: string
3435
CHALLENGES_PAGE: string

src-ts/lib/page-footer/PageFooter.tsx

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,17 @@ const PageFooter: FC<{}> = () => {
77

88
const navElementId: string = 'footer-nav-el'
99

10-
tcUniNav(
11-
'init',
12-
navElementId,
13-
{
14-
type: 'footer',
15-
},
16-
)
10+
// delay the initialization so
11+
// the nav element has time to render
12+
setTimeout(() => {
13+
tcUniNav(
14+
'init',
15+
navElementId,
16+
{
17+
type: 'footer',
18+
},
19+
)
20+
}, 10)
1721

1822
return <div id={navElementId} />
1923
}

src-ts/lib/profile-provider/profile-functions/profile-factory/profile.factory.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,24 @@ import { UserProfile } from '../../user-profile.model'
33

44
import { UserRole } from './user-role.enum'
55

6-
export function create(profile: UserProfile, token: TokenModel): UserProfile {
7-
// TODO: create the profile full name property
6+
export function create(profile: UserProfile, token: TokenModel, hasDiceEnabled: boolean): UserProfile {
7+
88
// Currently, the "Self-Service Customer" role is being set when a user is created
99
// during the self-service workflow. There are no other roles being set to distinguish
1010
// between Customers and Members.
1111
// Therefore, the only way to know if a user is a Member is if s/he is not a Customer.
1212
// This is imperfect, bc a user could be both a Customer or a Member, but for now
1313
// we are okay with this and will have a more in-depth initiave to properly assign
14-
// rolees.
14+
// roles.
1515
profile.isCustomer = !!token.roles?.some(role => role === UserRole.customer)
1616
profile.isMember = !profile.isCustomer
17+
18+
profile.isWipro = profile.email.endsWith('@wipro.com')
19+
profile.diceEnabled = hasDiceEnabled
20+
1721
// store roles for custom capability checks
18-
profile.roles = token.roles
22+
profile.roles = token.roles || []
23+
24+
// TODO: create the profile full name property
1925
return profile
2026
}

src-ts/lib/profile-provider/profile-functions/profile.functions.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { userGetDiceStatusAsync } from '../../functions/user-functions'
12
import { tokenGetAsync, TokenModel } from '../../functions/token-functions'
23
import { EditNameRequest } from '../edit-name-request.model'
34
import { UserProfile } from '../user-profile.model'
@@ -11,19 +12,22 @@ export async function getAsync(handle?: string): Promise<UserProfile | undefined
1112
const token: TokenModel = await tokenGetAsync()
1213

1314
// get the handle
14-
const safeHandle: string | undefined = handle || token?.handle
15-
if (!safeHandle) {
15+
const safeHandle: string | undefined = handle || token.handle
16+
if (!safeHandle || !token.userId) {
1617
return Promise.resolve(undefined)
1718
}
1819

1920
// get the profile
20-
const profileResult: UserProfile = await profileStoreGet(safeHandle)
21+
const profilePromise: Promise<UserProfile> = profileStoreGet(safeHandle)
22+
const dicePromise: Promise<boolean> = userGetDiceStatusAsync(token.userId)
23+
24+
const [profileResult, diceEnabled]: [UserProfile, boolean] = await Promise.all([profilePromise, dicePromise])
2125

2226
// make the changes we need based on the token
23-
const output: UserProfile = profileFactoryCreate(profileResult, token)
27+
const output: UserProfile = profileFactoryCreate(profileResult, token, diceEnabled)
2428
return output
2529
}
2630

27-
export async function editNameAsync(handle: string, profile: EditNameRequest): Promise<any> {
31+
export async function editNameAsync(handle: string, profile: EditNameRequest): Promise<UserProfile> {
2832
return profileStorePatchName(handle, profile)
2933
}

src-ts/lib/profile-provider/user-profile.model.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
export interface UserProfile {
22
competitionCountryCode: string
33
createdAt: number
4+
diceEnabled: boolean
45
email: string
56
firstName: string
67
handle: string
78
handleLower: string
89
homeCountryCode: string
910
isCustomer?: boolean
1011
isMember?: boolean
12+
isWipro: boolean
1113
lastName: string
1214
photoURL?: string
1315
roles: Array<string>

src-ts/tools/learn/course-details/course-curriculum/CourseCurriculum.tsx

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import {
2424

2525
import { CurriculumSummary } from './curriculum-summary'
2626
import { TcAcademyPolicyModal } from './tc-academy-policy-modal'
27+
import { DiceModal } from './dice-modal'
2728
import styles from './CourseCurriculum.module.scss'
2829

2930
interface CourseCurriculumProps {
@@ -37,11 +38,14 @@ interface CourseCurriculumProps {
3738
const CourseCurriculum: FC<CourseCurriculumProps> = (props: CourseCurriculumProps) => {
3839

3940
const navigate: NavigateFunction = useNavigate()
40-
const [searchParams]: any = useSearchParams()
41+
const [searchParams]: [URLSearchParams, unknown] = useSearchParams()
4142

4243
const isLoggedIn: boolean = !!props.profile
4344

44-
const [isTcAcademyPolicyModal, setIsTcAcademyPolicyModal]: [boolean, Dispatch<SetStateAction<boolean>>] = useState<boolean>(false)
45+
const [isTcAcademyPolicyModal, setIsTcAcademyPolicyModal]: [boolean, Dispatch<SetStateAction<boolean>>]
46+
= useState<boolean>(false)
47+
const [isDiceModalOpen, setIsDiceModalOpen]: [boolean, Dispatch<SetStateAction<boolean>>]
48+
= useState<boolean>(false)
4549

4650
const status: string = props.progress?.status ?? UserCertificationProgressStatus.inititialized
4751
const completedPercentage: number = (props.progress?.courseProgressPercentage ?? 0) / 100
@@ -76,6 +80,7 @@ const CourseCurriculum: FC<CourseCurriculumProps> = (props: CourseCurriculumProp
7680
* Handle user click on start course/resume/login button
7781
*/
7882
const handleStartCourseClick: () => void = useCallback(() => {
83+
7984
// if user is not logged in, redirect to login page
8085
if (!isLoggedIn) {
8186
// add a flag to the return url to show the academic policy modal
@@ -84,6 +89,13 @@ const CourseCurriculum: FC<CourseCurriculumProps> = (props: CourseCurriculumProp
8489
return
8590
}
8691

92+
// if the user is wipro and s/he hasn't set up DICE,
93+
// let the user know
94+
if (props.profile?.isWipro && !props.profile.diceEnabled) {
95+
setIsDiceModalOpen(true)
96+
return
97+
}
98+
8799
// Check if user accepted policy and resume(or start) the course
88100
if (props.progress?.academicHonestyPolicyAcceptedAt) {
89101
handleStartCourse()
@@ -92,6 +104,7 @@ const CourseCurriculum: FC<CourseCurriculumProps> = (props: CourseCurriculumProp
92104

93105
// show the academic policy modal before starting a new course
94106
setIsTcAcademyPolicyModal(true)
107+
// eslint-disable-next-line react-hooks/exhaustive-deps
95108
}, [
96109
handleStartCourse,
97110
isLoggedIn,
@@ -130,6 +143,7 @@ const CourseCurriculum: FC<CourseCurriculumProps> = (props: CourseCurriculumProp
130143
}
131144

132145
handleStartCourse()
146+
// eslint-disable-next-line react-hooks/exhaustive-deps
133147
}, [
134148
handleStartCourse,
135149
props.course.certificationId,
@@ -149,11 +163,20 @@ const CourseCurriculum: FC<CourseCurriculumProps> = (props: CourseCurriculumProp
149163
* proceed as if the user just clicked "Start course" button
150164
*/
151165
useEffect(() => {
166+
// eslint-disable-next-line no-null/no-null
152167
if (props.progressReady && isLoggedIn && searchParams.get(LEARN_PATHS.startCourseRouteFlag) !== null) {
153168
handleStartCourseClick()
154169
}
155170
}, [handleStartCourseClick, isLoggedIn, props.progressReady, searchParams])
156171

172+
function onAcademicHonestyModalClose(): void {
173+
setIsTcAcademyPolicyModal(false)
174+
}
175+
176+
function onDiceModalClose(): void {
177+
setIsDiceModalOpen(false)
178+
}
179+
157180
return (
158181
<>
159182
<div className={styles.wrap}>
@@ -198,9 +221,14 @@ const CourseCurriculum: FC<CourseCurriculumProps> = (props: CourseCurriculumProp
198221

199222
<TcAcademyPolicyModal
200223
isOpen={isTcAcademyPolicyModal}
201-
onClose={() => setIsTcAcademyPolicyModal(false)}
224+
onClose={onAcademicHonestyModalClose}
202225
onConfirm={handlePolicyAccept}
203226
/>
227+
228+
<DiceModal
229+
isOpen={isDiceModalOpen}
230+
onClose={onDiceModalClose}
231+
/>
204232
</>
205233
)
206234
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
@import '../../../../../lib/styles/includes';
2+
3+
.diceModal {
4+
5+
p {
6+
margin-bottom: $space-lg;
7+
8+
&.buttonContainer {
9+
display: flex;
10+
justify-content: center;
11+
}
12+
}
13+
}

0 commit comments

Comments
 (0)