Skip to content

Commit 4388a14

Browse files
authored
Merge pull request #1133 from topcoder-platform/diazz-admin-code-30377360
Topcoder Admin App - Add Terms Management Final fix
2 parents 8d4f168 + e17ed99 commit 4388a14

File tree

73 files changed

+3324
-28
lines changed

Some content is hidden

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

73 files changed

+3324
-28
lines changed

package.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
"@storybook/react": "7.6.10",
2929
"@stripe/react-stripe-js": "1.13.0",
3030
"@stripe/stripe-js": "1.41.0",
31+
"@tinymce/tinymce-react": "^6.2.1",
32+
"@types/codemirror": "5.60.15",
3133
"apexcharts": "^3.36.0",
3234
"axios": "^1.7.9",
3335
"browser-cookies": "^1.2.0",
@@ -43,6 +45,7 @@
4345
"draft-js-export-html": "^1.2.0",
4446
"draft-js-markdown-shortcuts-plugin": "^0.3.0",
4547
"draft-js-plugins-editor": "^2.0.3",
48+
"easymde": "2.20.0",
4649
"express": "^4.21.2",
4750
"express-fileupload": "^1.4.0",
4851
"express-interceptor": "^1.2.0",
@@ -101,6 +104,7 @@
101104
"styled-components": "^5.3.6",
102105
"swr": "^1.3.0",
103106
"tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.27",
107+
"tinymce": "^7.9.1",
104108
"typescript": "^4.8.4",
105109
"universal-navigation": "https://github.com/topcoder-platform/universal-navigation#9fc50d938be7182",
106110
"uuid": "^11.1.0",

src/apps/admin/src/admin-app.routes.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
permissionManagementRouteId,
1717
platformRouteId,
1818
rootRoute,
19+
termsRouteId,
1920
userManagementRouteId,
2021
} from './config/routes.config'
2122
import { platformSkillRouteId } from './platform/routes.config'
@@ -128,6 +129,22 @@ const BadgeListingPage: LazyLoadedComponent = lazyLoad(
128129
const CreateBadgePage: LazyLoadedComponent = lazyLoad(
129130
() => import('../../gamification-admin/src/pages/create-badge/CreateBadgePage'),
130131
)
132+
const TermsListPage: LazyLoadedComponent = lazyLoad(
133+
() => import('./platform/terms/TermsListPage'),
134+
'TermsListPage',
135+
)
136+
const TermsAddPage: LazyLoadedComponent = lazyLoad(
137+
() => import('./platform/terms/TermsAddPage'),
138+
'TermsAddPage',
139+
)
140+
const TermsEditPage: LazyLoadedComponent = lazyLoad(
141+
() => import('./platform/terms/TermsEditPage'),
142+
'TermsEditPage',
143+
)
144+
const TermsUsersPage: LazyLoadedComponent = lazyLoad(
145+
() => import('./platform/terms/TermsUsersPage'),
146+
'TermsUsersPage',
147+
)
131148

132149
export const toolTitle: string = ToolTitle.admin
133150

@@ -310,6 +327,22 @@ export const adminRoutes: ReadonlyArray<PlatformRoute> = [
310327
element: <BadgeDetailPage />,
311328
route: `${gamificationAdminRouteId}${baseDetailPath}/:id`,
312329
},
330+
{
331+
element: <TermsListPage />,
332+
route: termsRouteId,
333+
},
334+
{
335+
element: <TermsAddPage />,
336+
route: `${termsRouteId}/add`,
337+
},
338+
{
339+
element: <TermsUsersPage />,
340+
route: `${termsRouteId}/:id/users`,
341+
},
342+
{
343+
element: <TermsEditPage />,
344+
route: `${termsRouteId}/:id/edit`,
345+
},
313346
],
314347
element: <Platform />,
315348
id: platformRouteId,

src/apps/admin/src/config/routes.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export const userManagementRouteId = 'user-management'
1414
export const billingAccountRouteId = 'billing-account'
1515
export const permissionManagementRouteId = 'permission-management'
1616
export const gamificationAdminRouteId = 'gamification-admin'
17+
export const termsRouteId = 'terms'
1718
export const platformRouteId = 'platform'
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
.container {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 20px;
5+
position: relative;
6+
}
7+
8+
.blockForm {
9+
display: flex;
10+
flex-direction: column;
11+
gap: 20px;
12+
position: relative;
13+
}
14+
15+
.actionButtons {
16+
display: flex;
17+
justify-content: flex-end;
18+
gap: 6px;
19+
}
20+
21+
.dialogLoadingSpinnerContainer {
22+
position: absolute;
23+
width: 64px;
24+
display: flex;
25+
align-items: center;
26+
justify-content: center;
27+
bottom: 0;
28+
height: 64px;
29+
left: 0;
30+
31+
.spinner {
32+
background: none;
33+
}
34+
}
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/**
2+
* Dialog Add Term User.
3+
*/
4+
import { FC, useCallback } from 'react'
5+
import {
6+
Controller,
7+
ControllerRenderProps,
8+
useForm,
9+
UseFormReturn,
10+
} from 'react-hook-form'
11+
import _ from 'lodash'
12+
import classNames from 'classnames'
13+
14+
import { yupResolver } from '@hookform/resolvers/yup'
15+
import { BaseModal, Button, LoadingSpinner } from '~/libs/ui'
16+
17+
import { useEventCallback } from '../../hooks'
18+
import { UserTerm } from '../../models'
19+
import { FormAddTermUser } from '../../models/FormAddTermUser.model'
20+
import { formAddTermUserSchema } from '../../utils'
21+
import { FieldHandleSelect } from '../FieldHandleSelect'
22+
23+
import styles from './DialogAddTermUser.module.scss'
24+
25+
interface Props {
26+
className?: string
27+
open: boolean
28+
setOpen: (isOpen: boolean) => void
29+
termInfo: UserTerm
30+
isAdding: boolean
31+
doAddTermUser: (
32+
userId: number,
33+
userHandle: string,
34+
sucess: () => void,
35+
fail: () => void,
36+
) => void
37+
}
38+
39+
export const DialogAddTermUser: FC<Props> = (props: Props) => {
40+
const handleClose = useEventCallback(() => props.setOpen(false))
41+
const {
42+
handleSubmit,
43+
control,
44+
reset,
45+
formState: { errors, isValid, isDirty },
46+
}: UseFormReturn<FormAddTermUser> = useForm({
47+
defaultValues: {
48+
handle: undefined,
49+
},
50+
mode: 'all',
51+
resolver: yupResolver(formAddTermUserSchema),
52+
})
53+
54+
/**
55+
* Handle submit form event
56+
*/
57+
const onSubmit = useCallback(
58+
(data: FormAddTermUser) => {
59+
props.doAddTermUser(
60+
data.handle?.value ?? 0,
61+
data.handle?.label ?? '',
62+
() => {
63+
props.setOpen(false)
64+
},
65+
() => {
66+
reset({
67+
// eslint-disable-next-line unicorn/no-null
68+
handle: null, // only null will reset the handle field
69+
})
70+
},
71+
)
72+
},
73+
[props.doAddTermUser, reset],
74+
)
75+
76+
return (
77+
<BaseModal
78+
allowBodyScroll
79+
blockScroll
80+
title={`Sign Terms ${props.termInfo.title}`}
81+
onClose={handleClose}
82+
open={props.open}
83+
classNames={{
84+
modal: classNames(styles.modal),
85+
}}
86+
>
87+
<form
88+
className={classNames(styles.container, props.className)}
89+
onSubmit={handleSubmit(onSubmit)}
90+
>
91+
<div className={styles.blockForm}>
92+
<Controller
93+
name='handle'
94+
control={control}
95+
render={function render(controlProps: {
96+
field: ControllerRenderProps<
97+
FormAddTermUser,
98+
'handle'
99+
>
100+
}) {
101+
return (
102+
<FieldHandleSelect
103+
label='Handle'
104+
value={controlProps.field.value}
105+
onChange={controlProps.field.onChange}
106+
onBlur={controlProps.field.onBlur}
107+
classNameWrapper={styles.inputField}
108+
disabled={props.isAdding}
109+
dirty
110+
error={_.get(errors, 'handle.message')}
111+
/>
112+
)
113+
}}
114+
/>
115+
</div>
116+
<div className={styles.actionButtons}>
117+
<Button
118+
secondary
119+
size='lg'
120+
onClick={handleClose}
121+
disabled={props.isAdding}
122+
>
123+
Close
124+
</Button>
125+
<Button
126+
type='submit'
127+
primary
128+
size='lg'
129+
disabled={props.isAdding || !isValid || !isDirty}
130+
>
131+
Sign Terms
132+
</Button>
133+
</div>
134+
135+
{props.isAdding && (
136+
<div className={styles.dialogLoadingSpinnerContainer}>
137+
<LoadingSpinner className={styles.spinner} />
138+
</div>
139+
)}
140+
</form>
141+
</BaseModal>
142+
)
143+
}
144+
145+
export default DialogAddTermUser
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as DialogAddTermUser } from './DialogAddTermUser'

src/apps/admin/src/lib/components/FieldHandleSelect/FieldHandleSelect.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,9 @@ const fetchDatas = (
3434
interface Props {
3535
label?: string
3636
className?: string
37+
classNameWrapper?: string
3738
placeholder?: string
38-
readonly value?: SelectOption
39+
readonly value?: SelectOption | null
3940
readonly onChange?: (event: SelectOption) => void
4041
readonly disabled?: boolean
4142
readonly error?: string

src/apps/admin/src/lib/components/FieldSingleSelectAsync/FieldSingleSelectAsync.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ import styles from './FieldSingleSelectAsync.module.scss'
1616
interface Props {
1717
label?: string
1818
className?: string
19+
classNameWrapper?: string
1920
placeholder?: string
20-
readonly value?: SelectOption
21+
readonly value?: SelectOption | null
2122
readonly onChange?: (event: SelectOption) => void
2223
readonly disabled?: boolean
2324
readonly loadOptions?: (
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
@import '@libs/ui/styles/includes';
2+
3+
.container {
4+
display: flex;
5+
flex-direction: column;
6+
position: relative;
7+
}
8+
9+
.blockBtns {
10+
display: flex;
11+
gap: 15px;
12+
justify-content: flex-end;
13+
}
14+
15+
.blockActionLoading {
16+
position: absolute;
17+
width: 64px;
18+
display: flex;
19+
align-items: center;
20+
justify-content: center;
21+
height: 64px;
22+
left: $sp-8;
23+
bottom: $sp-8;
24+
25+
.spinner {
26+
background: none;
27+
}
28+
29+
@include ltelg {
30+
left: $sp-4;
31+
bottom: $sp-4;
32+
}
33+
}
34+
35+
.fieldTextContainer,
36+
.fieldTitle {
37+
grid-column: 1 / span 2;
38+
}
39+
40+
.fieldTextContainer {
41+
display: flex;
42+
flex-direction: column;
43+
align-items: flex-start;
44+
gap: 10px;
45+
}
46+
47+
.fieldAreaContainer {
48+
textarea {
49+
height: 200px;
50+
resize: none;
51+
}
52+
}
53+
54+
.fieldText {
55+
width: 100%;
56+
}
57+
58+
.btnDelete {
59+
display: flex;
60+
align-items: center;
61+
gap: 5px;
62+
63+
strong {
64+
font-weight: bold;
65+
}
66+
}

0 commit comments

Comments
 (0)