Skip to content

Topcoder Admin App - Add SSOLogin to User Management #1121

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

Merged
merged 2 commits into from
Jun 25, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@
position: relative;
}

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

.actionButtons {
display: flex;
justify-content: flex-end;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ export const DialogEditUserEmail: FC<Props> = (props: Props) => {
className={classNames(styles.container, props.className)}
onSubmit={handleSubmit(onSubmit)}
>
<div>
<div className={styles.blockForm}>

Choose a reason for hiding this comment

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

Consider verifying that styles.blockForm is defined and correctly imported to ensure it applies the intended styles.

<InputText
type='text'
name='id'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
.modal {
width: 800px !important;

Choose a reason for hiding this comment

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

Avoid using !important unless absolutely necessary, as it can make the CSS harder to maintain and override.

}

.container {
display: flex;
flex-direction: column;
gap: 20px;
position: relative;
align-items: flex-start;

th:first-child {
padding-left: 16px !important;

Choose a reason for hiding this comment

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

Consider removing !important from padding-left to maintain CSS specificity and avoid potential conflicts.

}
}

.tableCell {
white-space: break-spaces !important;

Choose a reason for hiding this comment

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

The use of !important for white-space can lead to specificity issues. Try to refactor the CSS to avoid using !important.

text-align: left !important;

Choose a reason for hiding this comment

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

Avoid using !important for text-align. Consider restructuring the CSS to achieve the desired effect without it.

}

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

.btnActions {

Choose a reason for hiding this comment

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

The .btnActions class appears to have the same styles as .actionButtons. Consider consolidating these classes to reduce redundancy.

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

.blockAction {
width: 120px;
}

.loadingSpinnerContainer {
position: relative;
height: 100px;

.spinner {
background: none;
}
}

.dialogLoadingSpinnerContainer {
position: absolute;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
bottom: 0;
height: 64px;
left: 0;

.spinner {
background: none;
}
}

.desktopTable {
td {
vertical-align: middle;
}
}

.mobileTable {
width: 100%;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
/**
* Dialog edit user groups.
*/
import { FC, useCallback, useMemo, useState } from 'react'
import _ from 'lodash'
import classNames from 'classnames'

import {
BaseModal,
Button,
LoadingSpinner,
Table,
TableColumn,
} from '~/libs/ui'
import { useWindowSize, WindowSize } from '~/libs/shared'

import {
MobileTableColumn,
SSOLoginProvider,
SSOUserLogin,
UserInfo,
} from '../../models'
import { useManageUserSSOLogin, useManageUserSSOLoginProps } from '../../hooks'
import { FormAddSSOLogin } from '../FormAddSSOLogin'
import { FormAddSSOLoginData } from '../../models/FormAddSSOLoginData.model'
import { TableMobile } from '../common/TableMobile'

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

interface Props {
className?: string
open: boolean
setOpen: (isOpen: boolean) => void
userInfo: UserInfo
providers: SSOLoginProvider[]
}

export const DialogEditUserSSOLogin: FC<Props> = (props: Props) => {
const { width: screenWidth }: WindowSize = useWindowSize()
const isTablet = useMemo(() => screenWidth <= 900, [screenWidth])
const {
ssoUserLogins,
isLoading: isFetching,
isAdding,
isRemoving,
doAddSSOUserLogin,
doUpdateSSOUserLogin,
doRemoveSSOUserLogin,
}: useManageUserSSOLoginProps = useManageUserSSOLogin(props.userInfo)
const isRemovingBool = useMemo(
() => _.some(isRemoving, value => value === true),
[isRemoving],
)
const isLoading = useMemo(
() => isFetching || isAdding || isRemovingBool,
[isFetching, isAdding, isRemovingBool],
)

const [showAddForm, setShowAddForm] = useState(false)
const [showEditForm, setShowEditForm] = useState<SSOUserLogin>()

const handleClose = useCallback(() => {
if (!isLoading) {
props.setOpen(false)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isLoading])

Choose a reason for hiding this comment

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

The useCallback dependency array is missing props.setOpen. Consider adding it to ensure the callback updates correctly when props.setOpen changes.

const columns = useMemo<TableColumn<SSOUserLogin>[]>(
() => [
{
className: styles.tableCell,
label: 'User ID',
propertyName: 'userId',
type: 'text',
},
{
className: styles.tableCell,
label: 'Name',
propertyName: 'name',
type: 'text',
},
{
className: styles.tableCell,
label: 'Provider',
propertyName: 'provider',
type: 'text',
},
{
className: styles.tableCell,
label: 'Email',
propertyName: 'email',
type: 'text',
},
{
className: styles.blockAction,
label: 'Actions',
renderer: (data: SSOUserLogin) => (
<div className={styles.btnActions}>
<Button
primary
variant='danger'
label='Remove'
onClick={function onClick() {
doRemoveSSOUserLogin(data)

Choose a reason for hiding this comment

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

Consider using an arrow function for the onClick handler to maintain consistency with other handlers in the code.

}}
disabled={isRemoving[data.provider]}
/>
<Button
primary
label='Edit'
onClick={function onClick() {

Choose a reason for hiding this comment

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

Consider using an arrow function for the onClick handler to maintain consistency with other handlers in the code.

setShowEditForm(data)
}}
disabled={isAdding}
/>
</div>
),
type: 'action',
},
],
[isAdding, isRemoving, doRemoveSSOUserLogin],
)

const columnsMobile = useMemo<MobileTableColumn<SSOUserLogin>[][]>(
() => columns.map(column => {
if (column.label === 'Actions') {
return [
{
...column,
colSpan: 2,
mobileType: 'last-value',
},
]
}

return [
{
...column,
className: '',
label: `${column.label as string} label`,
mobileType: 'label',
renderer: () => (
<div>
{column.label as string}
:
</div>
),
type: 'element',
},
{
...column,
mobileType: 'last-value',
},
]
}),
[columns],
)

const cancelEditForm = useCallback(() => {
setShowEditForm(undefined)
setShowAddForm(false)
}, [setShowAddForm, setShowEditForm])

return (
<BaseModal
allowBodyScroll
blockScroll
title={`SSO Logins of ${props.userInfo.handle}`}
onClose={handleClose}
open={props.open}
focusTrapped={false}
classNames={{
modal: classNames(styles.modal),
}}
>
<div className={classNames(styles.container, props.className)}>
{isFetching ? (
<div className={styles.loadingSpinnerContainer}>
<LoadingSpinner className={styles.spinner} />
</div>
) : (
<>
{ssoUserLogins.length ? (
<>
{isTablet ? (
<TableMobile
columns={columnsMobile}
data={ssoUserLogins}
className={styles.mobileTable}
/>
) : (
<Table
columns={columns}
data={ssoUserLogins}
disableSorting
onToggleSort={_.noop}
className={styles.desktopTable}
/>
)}
</>
) : (
<div>No SSO logins</div>
)}
</>
)}

{showAddForm || showEditForm ? (
<FormAddSSOLogin
isAdding={isAdding}
onSubmit={function onSubmit(data: FormAddSSOLoginData) {
if (showAddForm) {
doAddSSOUserLogin(data, cancelEditForm)
} else {
doUpdateSSOUserLogin(data, cancelEditForm)
}
}}
onCancel={cancelEditForm}
providers={props.providers}
editingData={showEditForm}
/>
) : (
<Button
primary
type='submit'
onClick={function onClick() {

Choose a reason for hiding this comment

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

Consider using an arrow function for the onClick handler to maintain consistency with other handlers in the code.

setShowAddForm(true)
}}
>
Add
</Button>
)}
<div className={styles.actionButtons}>
<Button
secondary
size='lg'
onClick={handleClose}
disabled={isLoading}
>
Close
</Button>
</div>

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

export default DialogEditUserSSOLogin
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as DialogEditUserSSOLogin } from './DialogEditUserSSOLogin'
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.container {
display: flex;
flex-direction: column;
gap: 20px;
position: relative;
width: 100%;
}

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