Skip to content

Topcoder Admin App - Add Terms Management Final fix #1133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: feat/system-admin
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"@storybook/react": "7.6.10",
"@stripe/react-stripe-js": "1.13.0",
"@stripe/stripe-js": "1.41.0",
"@tinymce/tinymce-react": "^6.2.1",
"@types/codemirror": "5.60.15",
"apexcharts": "^3.36.0",
"axios": "^1.7.9",
"browser-cookies": "^1.2.0",
Expand All @@ -43,6 +45,7 @@
"draft-js-export-html": "^1.2.0",
"draft-js-markdown-shortcuts-plugin": "^0.3.0",
"draft-js-plugins-editor": "^2.0.3",
"easymde": "2.20.0",
"express": "^4.21.2",
"express-fileupload": "^1.4.0",
"express-interceptor": "^1.2.0",
Expand Down Expand Up @@ -101,6 +104,7 @@
"styled-components": "^5.3.6",
"swr": "^1.3.0",
"tc-auth-lib": "topcoder-platform/tc-auth-lib#1.0.27",
"tinymce": "^7.9.1",
"typescript": "^4.8.4",
"universal-navigation": "https://github.com/topcoder-platform/universal-navigation#9fc50d938be7182",
"uuid": "^11.1.0",
Expand Down
33 changes: 33 additions & 0 deletions src/apps/admin/src/admin-app.routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
permissionManagementRouteId,
platformRouteId,
rootRoute,
termsRouteId,
userManagementRouteId,
} from './config/routes.config'
import { platformSkillRouteId } from './platform/routes.config'
Expand Down Expand Up @@ -128,6 +129,22 @@ const BadgeListingPage: LazyLoadedComponent = lazyLoad(
const CreateBadgePage: LazyLoadedComponent = lazyLoad(
() => import('../../gamification-admin/src/pages/create-badge/CreateBadgePage'),
)
const TermsListPage: LazyLoadedComponent = lazyLoad(
() => import('./platform/terms/TermsListPage'),
'TermsListPage',
)
const TermsAddPage: LazyLoadedComponent = lazyLoad(
() => import('./platform/terms/TermsAddPage'),
'TermsAddPage',
)
const TermsEditPage: LazyLoadedComponent = lazyLoad(
() => import('./platform/terms/TermsEditPage'),
'TermsEditPage',
)
const TermsUsersPage: LazyLoadedComponent = lazyLoad(
() => import('./platform/terms/TermsUsersPage'),
'TermsUsersPage',
)

export const toolTitle: string = ToolTitle.admin

Expand Down Expand Up @@ -310,6 +327,22 @@ export const adminRoutes: ReadonlyArray<PlatformRoute> = [
element: <BadgeDetailPage />,
route: `${gamificationAdminRouteId}${baseDetailPath}/:id`,
},
{
element: <TermsListPage />,
route: termsRouteId,
},
{
element: <TermsAddPage />,
route: `${termsRouteId}/add`,
},
{
element: <TermsUsersPage />,
route: `${termsRouteId}/:id/users`,
},
{
element: <TermsEditPage />,
route: `${termsRouteId}/:id/edit`,
},
],
element: <Platform />,
id: platformRouteId,
Expand Down
1 change: 1 addition & 0 deletions src/apps/admin/src/config/routes.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export const userManagementRouteId = 'user-management'
export const billingAccountRouteId = 'billing-account'
export const permissionManagementRouteId = 'permission-management'
export const gamificationAdminRouteId = 'gamification-admin'
export const termsRouteId = 'terms'
export const platformRouteId = 'platform'
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.container {
display: flex;
flex-direction: column;
gap: 20px;
position: relative;
}

.blockForm {
display: flex;
flex-direction: column;
gap: 20px;
position: relative;
}

.actionButtons {
display: flex;
justify-content: flex-end;
gap: 6px;
}

.dialogLoadingSpinnerContainer {
position: absolute;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive class name for .dialogLoadingSpinnerContainer to clearly indicate its purpose or context within the dialog.

width: 64px;
display: flex;
align-items: center;
justify-content: center;
bottom: 0;
height: 64px;
left: 0;

.spinner {
background: none;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The .spinner class currently only sets background: none;. If there are additional styles needed for the spinner, consider adding them here for clarity and completeness.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/**
* Dialog Add Term User.
*/
import { FC, useCallback } from 'react'
import {
Controller,
ControllerRenderProps,
useForm,
UseFormReturn,
} from 'react-hook-form'
import _ from 'lodash'
import classNames from 'classnames'

import { yupResolver } from '@hookform/resolvers/yup'
import { BaseModal, Button, LoadingSpinner } from '~/libs/ui'

import { useEventCallback } from '../../hooks'
import { UserTerm } from '../../models'
import { FormAddTermUser } from '../../models/FormAddTermUser.model'
import { formAddTermUserSchema } from '../../utils'
import { FieldHandleSelect } from '../FieldHandleSelect'

import styles from './DialogAddTermUser.module.scss'

interface Props {
className?: string
open: boolean
setOpen: (isOpen: boolean) => void
termInfo: UserTerm
isAdding: boolean
doAddTermUser: (
userId: number,
userHandle: string,
sucess: () => void,
fail: () => void,
) => void
}

export const DialogAddTermUser: FC<Props> = (props: Props) => {
const handleClose = useEventCallback(() => props.setOpen(false))
const {
handleSubmit,
control,
reset,
formState: { errors, isValid, isDirty },
}: UseFormReturn<FormAddTermUser> = useForm({
defaultValues: {
handle: undefined,
},
mode: 'all',
resolver: yupResolver(formAddTermUserSchema),
})

/**
* Handle submit form event
*/
const onSubmit = useCallback(
(data: FormAddTermUser) => {
props.doAddTermUser(
data.handle?.value ?? 0,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider checking if data.handle is defined before accessing value and label to avoid potential runtime errors.

data.handle?.label ?? '',
() => {
props.setOpen(false)
},
() => {
reset({
// eslint-disable-next-line unicorn/no-null
handle: null, // only null will reset the handle field
})
},
)
},
[props.doAddTermUser, reset],
)

return (
<BaseModal
allowBodyScroll
blockScroll
title={`Sign Terms ${props.termInfo.title}`}
onClose={handleClose}
open={props.open}
classNames={{
modal: classNames(styles.modal),
}}
>
<form
className={classNames(styles.container, props.className)}
onSubmit={handleSubmit(onSubmit)}
>
<div className={styles.blockForm}>
<Controller
name='handle'
control={control}
render={function render(controlProps: {
field: ControllerRenderProps<
FormAddTermUser,
'handle'
>
}) {
return (
<FieldHandleSelect
label='Handle'
value={controlProps.field.value}
onChange={controlProps.field.onChange}
onBlur={controlProps.field.onBlur}
classNameWrapper={styles.inputField}
disabled={props.isAdding}
dirty

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dirty prop in FieldHandleSelect is set to true, but it is not clear if this prop is necessary or used within the component. Verify if this prop is needed and remove it if it is not used.

error={_.get(errors, 'handle.message')}
/>
)
}}
/>
</div>
<div className={styles.actionButtons}>
<Button
secondary
size='lg'
onClick={handleClose}
disabled={props.isAdding}
>
Close
</Button>
<Button
type='submit'
primary
size='lg'
disabled={props.isAdding || !isValid || !isDirty}
>
Sign Terms
</Button>
</div>

{props.isAdding && (
<div className={styles.dialogLoadingSpinnerContainer}>
<LoadingSpinner className={styles.spinner} />
</div>
)}
</form>
</BaseModal>
)
}

export default DialogAddTermUser
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as DialogAddTermUser } from './DialogAddTermUser'
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ const fetchDatas = (
interface Props {
label?: string
className?: string
classNameWrapper?: string
placeholder?: string
readonly value?: SelectOption
readonly value?: SelectOption | null

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider updating the type definition for value to SelectOption | null in any other related interfaces or components that might be affected by this change to ensure consistency across the codebase.

readonly onChange?: (event: SelectOption) => void
readonly disabled?: boolean
readonly error?: string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ import styles from './FieldSingleSelectAsync.module.scss'
interface Props {
label?: string
className?: string
classNameWrapper?: string

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding a description for the new classNameWrapper prop in the component's documentation or prop types to clarify its purpose and usage.

placeholder?: string
readonly value?: SelectOption
readonly value?: SelectOption | null
readonly onChange?: (event: SelectOption) => void
readonly disabled?: boolean
readonly loadOptions?: (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
@import '@libs/ui/styles/includes';

.container {
display: flex;
flex-direction: column;
position: relative;
}

.blockBtns {
display: flex;
gap: 15px;
justify-content: flex-end;
}

.blockActionLoading {
position: absolute;
width: 64px;
display: flex;
align-items: center;
justify-content: center;
height: 64px;
left: $sp-8;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name instead of $sp-8 to improve readability and maintainability.

bottom: $sp-8;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name instead of $sp-8 to improve readability and maintainability.


.spinner {
background: none;
}

@include ltelg {
left: $sp-4;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name instead of $sp-4 to improve readability and maintainability.

bottom: $sp-4;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider using a more descriptive variable name instead of $sp-4 to improve readability and maintainability.

}
}

.fieldTextContainer,
.fieldTitle {
grid-column: 1 / span 2;
}

.fieldTextContainer {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
}

.fieldAreaContainer {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new class .fieldAreaContainer is added but not used in this diff. Ensure that it is utilized in the component or remove it if unnecessary.

textarea {
height: 200px;
resize: none;
}
}

.fieldText {
width: 100%;
}

.btnDelete {
display: flex;
align-items: center;
gap: 5px;

strong {
font-weight: bold;
}
}
Loading