Skip to content

Commit 91493ee

Browse files
authored
Merge pull request #554 from topcoder-platform/dev
3.0 TCA Certifications - 2023-03-01 -> master
2 parents 1b00073 + 04acd1f commit 91493ee

File tree

378 files changed

+8438
-2825
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

378 files changed

+8438
-2825
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ By overriding the app to use <b>port 443</b>, you can use the authorized URL and
153153

154154
>**NOTE:** Mac users will require running the app with elevated permissions in order to use a port lower than 500.
155155
156+
Easy way to overcome elevated permissions is to make use of:
157+
158+
```
159+
sudo setcap 'cap_net_bind_service=+ep' `which node`
160+
```
161+
156162
For easier development, it is recommended that you add this certificate to your trusted root authorities and as a trused cert in your browser. Google your browser and OS for more info on how to trust cert authorities.
157163

158164
Otherwise, you will need to override the exception each time you load the site. Firefox users may need to user an incognito browser in order to override the exception.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@topcoder-platform/platform-ui",
3-
"version": "2.0.7",
3+
"version": "3.0.0",
44
"private": true,
55
"scripts": {
66
"dev": "yarn react-app-rewired start",
@@ -42,6 +42,7 @@
4242
"moment": "^2.29.4",
4343
"moment-timezone": "^0.5.37",
4444
"prop-types": "^15.8.1",
45+
"qrcode.react": "^3.1.0",
4546
"qs": "^6.11.0",
4647
"rc-checkbox": "^2.3.2",
4748
"react": "^18.2.0",

src-ts/.eslintrc.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ module.exports = {
7373
],
7474
'complexity': [
7575
'error',
76-
11
76+
14
7777
],
7878
'import/extensions': 'off',
7979
'import/no-named-default': 'off',

src-ts/App.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { Routes } from 'react-router-dom'
33
import { toast, ToastContainer } from 'react-toastify'
44

55
import { Header } from './header'
6-
import { routeContext, RouteContextData } from './lib'
6+
import { routeContext, RouteContextData, useViewportUnitsFix } from './lib'
77

88
const App: FC<{}> = () => {
99

@@ -12,6 +12,8 @@ const App: FC<{}> = () => {
1212
const routeElements: Array<ReactElement> = allRoutes
1313
.map(route => getRouteElement(route))
1414

15+
useViewportUnitsFix()
16+
1517
return (
1618
<>
1719
<Header />

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { EnvironmentConfigModel } from './environment-config.model'
33

44
const COMMUNITY_WEBSITE: string = 'https://www.topcoder-dev.com'
5+
const TCA_WEBSITE: string = 'https://platform-ui.topcoder-dev.com'
56

67
export const EnvironmentConfigDefault: EnvironmentConfigModel = {
78
ANALYTICS: {
@@ -36,11 +37,13 @@ export const EnvironmentConfigDefault: EnvironmentConfigModel = {
3637
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.jl6Lp_friVNwEP8nfsfmL-vrQFzOFp2IfM_HC7AwGcg',
3738
},
3839
TOPCODER_URLS: {
40+
ACCOUNT_PROFILE: `${COMMUNITY_WEBSITE}/settings/profile`,
3941
ACCOUNT_SETTINGS: `${COMMUNITY_WEBSITE}/settings/account`,
4042
API_BASE: `${COMMUNITY_WEBSITE}/api`,
4143
BLOG_PAGE: `${COMMUNITY_WEBSITE}/blog`,
4244
CHALLENGES_PAGE: `${COMMUNITY_WEBSITE}/challenges`,
4345
GIGS_PAGE: `${COMMUNITY_WEBSITE}/gigs`,
46+
TCA: `${TCA_WEBSITE}`,
4447
THRIVE_PAGE: `${COMMUNITY_WEBSITE}/thrive`,
4548
USER_PROFILE: `${COMMUNITY_WEBSITE}/members`,
4649
WP_CONTENT: `${COMMUNITY_WEBSITE}/wp-content`,

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { EnvironmentConfigModel } from './environment-config.model'
33
import { EnvironmentConfigDefault } from './environment.default.config'
44

55
const COMMUNITY_WEBSITE: string = 'https://www.topcoder.com'
6+
const TCA_WEBSITE: string = 'https://platform-ui.topcoder.com'
67

78
export const EnvironmentConfigProd: EnvironmentConfigModel = {
89
...EnvironmentConfigDefault,
@@ -34,11 +35,13 @@ export const EnvironmentConfigProd: EnvironmentConfigModel = {
3435
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJyb2xlcyI6WyJUb3Bjb2RlciBVc2VyIl0sImlzcyI6Imh0dHBzOi8vYXBpLnRvcGNvZGVyLWRldi5jb20iLCJoYW5kbGUiOiJ0ZXN0MSIsImV4cCI6MjU2MzA3NjY4OSwidXNlcklkIjoiNDAwNTEzMzMiLCJpYXQiOjE0NjMwNzYwODksImVtYWlsIjoidGVzdEB0b3Bjb2Rlci5jb20iLCJqdGkiOiJiMzNiNzdjZC1iNTJlLTQwZmUtODM3ZS1iZWI4ZTBhZTZhNGEifQ.jl6Lp_friVNwEP8nfsfmL-vrQFzOFp2IfM_HC7AwGcg',
3536
},
3637
TOPCODER_URLS: {
38+
ACCOUNT_PROFILE: `${COMMUNITY_WEBSITE}/settings/profile`,
3739
ACCOUNT_SETTINGS: `${COMMUNITY_WEBSITE}/settings/account`,
3840
API_BASE: `${COMMUNITY_WEBSITE}/api`,
3941
BLOG_PAGE: `${COMMUNITY_WEBSITE}/blog`,
4042
CHALLENGES_PAGE: `${COMMUNITY_WEBSITE}/challenges`,
4143
GIGS_PAGE: `${COMMUNITY_WEBSITE}/gigs`,
44+
TCA: `${TCA_WEBSITE}`,
4245
THRIVE_PAGE: `${COMMUNITY_WEBSITE}/thrive`,
4346
USER_PROFILE: `${COMMUNITY_WEBSITE}/members`,
4447
WP_CONTENT: `${COMMUNITY_WEBSITE}/wp-content`,

src-ts/header/Header.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ const Header: FC = () => {
145145

146146
return (
147147
<>
148-
<div id={navElementId} />
149148
<div
150149
id={PageSubheaderPortalId}
151150
className={classNames('full-width-relative', !ready && 'hidden')}

src-ts/lib/breadcrumb/Breadcrumb.module.scss

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,12 @@
3838
display: inline-flex;
3939
align-items: center;
4040
padding: 0;
41-
41+
@include ltesm {
42+
flex: 0 1 auto;
43+
&:first-child {
44+
flex: 1 0 auto;
45+
}
46+
}
4247
a {
4348
@extend .overline;
4449
display: block;

src-ts/lib/button/Button.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { IconOutline } from '../svgs'
88
import '../styles/index.scss'
99

1010
export type ButtonSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl'
11-
export type ButtonStyle = 'icon' | 'icon-bordered' | 'link' | 'primary' | 'secondary' | 'tertiary' | 'text'
11+
export type ButtonStyle = 'icon' | 'icon-bordered' | 'link' | 'primary' | 'secondary' | 'tertiary' | 'text' | 'outline'
1212
export type ButtonType = 'button' | 'submit'
1313

1414
export interface ButtonProps {
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* eslint-disable max-len */
2+
import { FC } from 'react'
3+
4+
const DefaultMemberIcon: FC = () => (
5+
<svg
6+
xmlns='http://www.w3.org/2000/svg'
7+
xmlnsXlink='http://www.w3.org/1999/xlink'
8+
width='141'
9+
height='141'
10+
viewBox='0 0 141 141'
11+
>
12+
<defs>
13+
<rect
14+
id='path-1'
15+
width='140'
16+
height='140'
17+
x='0.8'
18+
y='0.5'
19+
rx='306'
20+
/>
21+
</defs>
22+
<g fill='none' fillRule='evenodd' stroke='none' strokeWidth='1'>
23+
<g transform='translate(-205 -203) translate(205 203)'>
24+
<mask id='mask-2' fill='#fff'>
25+
<use xlinkHref='#path-1' />
26+
</mask>
27+
<use fill='#F0F0F0' xlinkHref='#path-1' />
28+
<path
29+
fill='#A3A3AE'
30+
stroke='#A3A3AE'
31+
strokeWidth='3'
32+
d='M118 137.143c0 4.345-3.501 7.857-7.833 7.857H31.833C27.51 145 24 141.488 24 137.143c0-15.714 15.142-30.376 30.62-36.174-8.937-5.54-14.953-15.377-14.953-26.683v-7.857C39.667 49.072 53.697 35 71 35c17.304 0 31.333 14.072 31.333 31.429v7.857c0 11.306-6.016 21.143-14.946 26.683 15.471 5.798 30.613 20.46 30.613 36.174h0z'
33+
mask='url(#mask-2)'
34+
opacity='0.2'
35+
/>
36+
</g>
37+
</g>
38+
</svg>
39+
)
40+
41+
export default DefaultMemberIcon
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as DefaultMemberIcon } from './DefaultMemberIcon'

src-ts/lib/form/Form.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
Dispatch,
55
FocusEvent,
66
FormEvent,
7+
ReactNode,
78
RefObject,
89
SetStateAction,
910
useEffect,
@@ -31,6 +32,7 @@ import styles from './Form.module.scss'
3132

3233
interface FormProps<ValueType, RequestType> {
3334
readonly action?: FormAction // only type submit will perform validation
35+
readonly children?: ReactNode
3436
readonly formDef: FormDefinition
3537
readonly formValues?: ValueType
3638
readonly onChange?: (inputs: ReadonlyArray<FormInputModel>) => void,
@@ -219,6 +221,8 @@ const Form: <ValueType extends FormValue, RequestType extends FormValue>(props:
219221
onChange={onChange}
220222
/>
221223

224+
{props.children}
225+
222226
<div className={classNames(styles['form-footer'], 'form-footer')}>
223227
{!!formError && (
224228
<div
@@ -229,7 +233,7 @@ const Form: <ValueType extends FormValue, RequestType extends FormValue>(props:
229233
{formError}
230234
</div>
231235
)}
232-
<div className={styles['button-container']}>
236+
<div className={classNames('form-button-container', styles['button-container'])}>
233237
<div className={styles['left-container']}>
234238
{secondaryGroupButtons}
235239
</div>

src-ts/lib/form/form-definition.model.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export interface FormDefinition {
1313
readonly groupsOptions?: FormGroupOptions
1414
readonly shortName?: string
1515
readonly subtitle?: string
16-
readonly successMessage?: string
16+
readonly successMessage?: string | boolean
1717
readonly tabIndexStart?: number
1818
readonly title?: string
1919
}

src-ts/lib/form/form-functions/form.functions.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,10 +122,13 @@ export async function onSubmitAsync<T extends FormValue>(
122122

123123
return savePromise
124124
.then(() => {
125-
const safeSuccessMessage: string = !!successMessage
126-
? successMessage as string
127-
: `Your ${shortName || 'data'} has been saved.`
128-
toast.success(safeSuccessMessage)
125+
if (successMessage !== false) {
126+
const safeSuccessMessage: string = !!successMessage
127+
? successMessage as string
128+
: `Your ${shortName || 'data'} has been saved.`
129+
toast.success(safeSuccessMessage)
130+
}
131+
129132
onSuccess?.()
130133
})
131134
.catch(error => Promise.reject(error.response?.data?.result?.content ?? error.message ?? error))

src-ts/lib/form/form-groups/form-input/input-text/InputText.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface InputTextProps {
2323
readonly onBlur?: (event: FocusEvent<HTMLInputElement>) => void
2424
readonly onChange: (event: FocusEvent<HTMLInputElement>) => void
2525
readonly placeholder?: string
26+
readonly readonly?: boolean
2627
readonly spellCheck?: boolean
2728
readonly tabIndex: number
2829
readonly type: InputTextTypes
@@ -53,6 +54,7 @@ const InputText: FC<InputTextProps> = (props: InputTextProps) => {
5354
onChange={props.onChange}
5455
name={props.name}
5556
placeholder={props.placeholder}
57+
readOnly={props.readonly}
5658
spellCheck={!!props.spellCheck}
5759
tabIndex={props.tabIndex}
5860
type={props.type || 'text'}

src-ts/lib/form/form-input.model.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface FormInputModel {
4444
readonly notTabbable?: boolean
4545
options?: ReadonlyArray<FormRadioButtonOption>
4646
readonly placeholder?: string
47+
readonly readonly?: boolean
4748
readonly spellCheck?: boolean
4849
readonly title?: string
4950
touched?: boolean
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
.wrap {
2+
flex: 1;
3+
display: flex;
4+
flex-direction: column;
5+
align-items: center;
6+
justify-content: center;
7+
8+
h3 {
9+
margin-bottom: 32px;
10+
}
11+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { FC, useEffect } from 'react'
2+
3+
import { Button } from '../button'
4+
5+
import styles from './GenericPageMoved.module.scss'
6+
7+
interface GenericPageMovedProps {
8+
pageTitle: string
9+
newPageUrl: string
10+
}
11+
12+
const GenericPageMoved: FC<GenericPageMovedProps> = (props: GenericPageMovedProps) => {
13+
14+
// setup auto redirect in 5sec.
15+
useEffect(() => {
16+
const to: ReturnType<typeof setTimeout> = setTimeout(() => {
17+
window.location.href = props.newPageUrl
18+
}, 5000)
19+
20+
return () => clearTimeout(to)
21+
}, [props.newPageUrl])
22+
23+
return (
24+
<div className={styles.wrap}>
25+
<h3>This page has moved.</h3>
26+
<Button
27+
label={`Navigate to ${props.pageTitle}`}
28+
url={props.newPageUrl}
29+
/>
30+
<p>We will automatically redirect you in 5 seconds...</p>
31+
</div>
32+
)
33+
}
34+
35+
export default GenericPageMoved
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as GenericPageMoved } from './GenericPageMoved'

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,14 @@ export interface GlobalConfig {
2929
CUSTOMER_TOKEN: string
3030
}
3131
TOPCODER_URLS: {
32+
ACCOUNT_PROFILE: string
3233
ACCOUNT_SETTINGS: string
3334
API_BASE: string
3435
BLOG_PAGE: string
3536
CHALLENGES_PAGE: string
3637
GIGS_PAGE: string
3738
THRIVE_PAGE: string
39+
TCA: string
3840
USER_PROFILE: string
3941
WP_CONTENT: string
4042
}

src-ts/lib/hooks/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,6 @@ export * from './use-check-is-mobile.hook'
22
export * from './use-click-outside.hook'
33
export * from './use-on-hover-element.hook'
44
export * from './use-storage.hook'
5+
export * from './use-viewport-units-fix.hook'
56
export * from './use-window-size.hook'
7+
export * from './use-navigate-back.hook'
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { NavigateFunction, useNavigate } from 'react-router-dom'
2+
3+
export type NavigateBackFunction = (fallbackUrl: string) => void
4+
type useNavigateBackType = () => NavigateBackFunction
5+
6+
export const useNavigateBack: useNavigateBackType = (): NavigateBackFunction => {
7+
const navigate: NavigateFunction = useNavigate()
8+
return (fallbackUrl: string) => {
9+
const currentPageHref: string = window.location.href
10+
11+
window.history.go(-1)
12+
13+
setTimeout(() => {
14+
// go back didn't work, navigate to desired fallback url
15+
if (window.location.href === currentPageHref) {
16+
navigate(fallbackUrl)
17+
}
18+
}, 30)
19+
}
20+
}

src-ts/lib/hooks/use-storage.hook.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Dispatch, SetStateAction, useCallback, useState } from 'react'
1+
import { Dispatch, MutableRefObject, SetStateAction, useCallback, useRef, useState } from 'react'
22

33
type StorageTypes = 'localStorage' | 'sessionStorage'
44

@@ -8,6 +8,7 @@ export function useStorage<T>(
88
initialValue?: T,
99
): [T, Dispatch<SetStateAction<T>>] {
1010
const storage: Storage = window[storageType]
11+
const wasKey: MutableRefObject<string> = useRef(storageKey)
1112

1213
const readStoredValue: () => T = useCallback(() => {
1314
try {
@@ -23,7 +24,17 @@ export function useStorage<T>(
2324

2425
// State to store our value
2526
// Pass initial state function to useState so logic is only executed once
26-
const [storedValue, setStoredValue]: [T, Dispatch<SetStateAction<T>>] = useState(readStoredValue())
27+
const rawStoredValue: T = readStoredValue()
28+
const [storedValue, setStoredValue]: [T, Dispatch<SetStateAction<T>>] = useState(rawStoredValue)
29+
30+
// update value when storage key changes
31+
if (wasKey.current !== storageKey) {
32+
wasKey.current = storageKey
33+
34+
if (rawStoredValue !== storedValue) {
35+
setTimeout(setStoredValue, 0, rawStoredValue)
36+
}
37+
}
2738

2839
// Return a wrapped version of useState's setter function that
2940
// persists the new value to local or session storage.

0 commit comments

Comments
 (0)