Skip to content

Commit c84f82c

Browse files
authored
Merge pull request #1121 from topcoder-platform/diazz-f2f-30377270
Topcoder Admin App - Add SSOLogin to User Management
2 parents 433c8c1 + 75c7fb5 commit c84f82c

17 files changed

+1036
-4
lines changed

src/apps/admin/src/lib/components/DialogEditUserEmail/DialogEditUserEmail.module.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,13 @@
55
position: relative;
66
}
77

8+
.blockForm {
9+
display: flex;
10+
flex-direction: column;
11+
gap: 20px;
12+
position: relative;
13+
}
14+
815
.actionButtons {
916
display: flex;
1017
justify-content: flex-end;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export const DialogEditUserEmail: FC<Props> = (props: Props) => {
6767
className={classNames(styles.container, props.className)}
6868
onSubmit={handleSubmit(onSubmit)}
6969
>
70-
<div>
70+
<div className={styles.blockForm}>
7171
<InputText
7272
type='text'
7373
name='id'
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
.modal {
2+
width: 800px !important;
3+
}
4+
5+
.container {
6+
display: flex;
7+
flex-direction: column;
8+
gap: 20px;
9+
position: relative;
10+
align-items: flex-start;
11+
12+
th:first-child {
13+
padding-left: 16px !important;
14+
}
15+
}
16+
17+
.tableCell {
18+
white-space: break-spaces !important;
19+
text-align: left !important;
20+
}
21+
22+
.actionButtons {
23+
display: flex;
24+
justify-content: flex-end;
25+
gap: 6px;
26+
width: 100%;
27+
}
28+
29+
.btnActions {
30+
display: flex;
31+
justify-content: flex-end;
32+
gap: 6px;
33+
}
34+
35+
.blockAction {
36+
width: 120px;
37+
}
38+
39+
.loadingSpinnerContainer {
40+
position: relative;
41+
height: 100px;
42+
43+
.spinner {
44+
background: none;
45+
}
46+
}
47+
48+
.dialogLoadingSpinnerContainer {
49+
position: absolute;
50+
width: 100%;
51+
display: flex;
52+
align-items: center;
53+
justify-content: center;
54+
bottom: 0;
55+
height: 64px;
56+
left: 0;
57+
58+
.spinner {
59+
background: none;
60+
}
61+
}
62+
63+
.desktopTable {
64+
td {
65+
vertical-align: middle;
66+
}
67+
}
68+
69+
.mobileTable {
70+
width: 100%;
71+
}
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/**
2+
* Dialog edit user groups.
3+
*/
4+
import { FC, useCallback, useMemo, useState } from 'react'
5+
import _ from 'lodash'
6+
import classNames from 'classnames'
7+
8+
import {
9+
BaseModal,
10+
Button,
11+
LoadingSpinner,
12+
Table,
13+
TableColumn,
14+
} from '~/libs/ui'
15+
import { useWindowSize, WindowSize } from '~/libs/shared'
16+
17+
import {
18+
MobileTableColumn,
19+
SSOLoginProvider,
20+
SSOUserLogin,
21+
UserInfo,
22+
} from '../../models'
23+
import { useManageUserSSOLogin, useManageUserSSOLoginProps } from '../../hooks'
24+
import { FormAddSSOLogin } from '../FormAddSSOLogin'
25+
import { FormAddSSOLoginData } from '../../models/FormAddSSOLoginData.model'
26+
import { TableMobile } from '../common/TableMobile'
27+
28+
import styles from './DialogEditUserSSOLogin.module.scss'
29+
30+
interface Props {
31+
className?: string
32+
open: boolean
33+
setOpen: (isOpen: boolean) => void
34+
userInfo: UserInfo
35+
providers: SSOLoginProvider[]
36+
}
37+
38+
export const DialogEditUserSSOLogin: FC<Props> = (props: Props) => {
39+
const { width: screenWidth }: WindowSize = useWindowSize()
40+
const isTablet = useMemo(() => screenWidth <= 900, [screenWidth])
41+
const {
42+
ssoUserLogins,
43+
isLoading: isFetching,
44+
isAdding,
45+
isRemoving,
46+
doAddSSOUserLogin,
47+
doUpdateSSOUserLogin,
48+
doRemoveSSOUserLogin,
49+
}: useManageUserSSOLoginProps = useManageUserSSOLogin(props.userInfo)
50+
const isRemovingBool = useMemo(
51+
() => _.some(isRemoving, value => value === true),
52+
[isRemoving],
53+
)
54+
const isLoading = useMemo(
55+
() => isFetching || isAdding || isRemovingBool,
56+
[isFetching, isAdding, isRemovingBool],
57+
)
58+
59+
const [showAddForm, setShowAddForm] = useState(false)
60+
const [showEditForm, setShowEditForm] = useState<SSOUserLogin>()
61+
62+
const handleClose = useCallback(() => {
63+
if (!isLoading) {
64+
props.setOpen(false)
65+
}
66+
// eslint-disable-next-line react-hooks/exhaustive-deps
67+
}, [isLoading])
68+
const columns = useMemo<TableColumn<SSOUserLogin>[]>(
69+
() => [
70+
{
71+
className: styles.tableCell,
72+
label: 'User ID',
73+
propertyName: 'userId',
74+
type: 'text',
75+
},
76+
{
77+
className: styles.tableCell,
78+
label: 'Name',
79+
propertyName: 'name',
80+
type: 'text',
81+
},
82+
{
83+
className: styles.tableCell,
84+
label: 'Provider',
85+
propertyName: 'provider',
86+
type: 'text',
87+
},
88+
{
89+
className: styles.tableCell,
90+
label: 'Email',
91+
propertyName: 'email',
92+
type: 'text',
93+
},
94+
{
95+
className: styles.blockAction,
96+
label: 'Actions',
97+
renderer: (data: SSOUserLogin) => (
98+
<div className={styles.btnActions}>
99+
<Button
100+
primary
101+
variant='danger'
102+
label='Remove'
103+
onClick={function onClick() {
104+
doRemoveSSOUserLogin(data)
105+
}}
106+
disabled={isRemoving[data.provider]}
107+
/>
108+
<Button
109+
primary
110+
label='Edit'
111+
onClick={function onClick() {
112+
setShowEditForm(data)
113+
}}
114+
disabled={isAdding}
115+
/>
116+
</div>
117+
),
118+
type: 'action',
119+
},
120+
],
121+
[isAdding, isRemoving, doRemoveSSOUserLogin],
122+
)
123+
124+
const columnsMobile = useMemo<MobileTableColumn<SSOUserLogin>[][]>(
125+
() => columns.map(column => {
126+
if (column.label === 'Actions') {
127+
return [
128+
{
129+
...column,
130+
colSpan: 2,
131+
mobileType: 'last-value',
132+
},
133+
]
134+
}
135+
136+
return [
137+
{
138+
...column,
139+
className: '',
140+
label: `${column.label as string} label`,
141+
mobileType: 'label',
142+
renderer: () => (
143+
<div>
144+
{column.label as string}
145+
:
146+
</div>
147+
),
148+
type: 'element',
149+
},
150+
{
151+
...column,
152+
mobileType: 'last-value',
153+
},
154+
]
155+
}),
156+
[columns],
157+
)
158+
159+
const cancelEditForm = useCallback(() => {
160+
setShowEditForm(undefined)
161+
setShowAddForm(false)
162+
}, [setShowAddForm, setShowEditForm])
163+
164+
return (
165+
<BaseModal
166+
allowBodyScroll
167+
blockScroll
168+
title={`SSO Logins of ${props.userInfo.handle}`}
169+
onClose={handleClose}
170+
open={props.open}
171+
focusTrapped={false}
172+
classNames={{
173+
modal: classNames(styles.modal),
174+
}}
175+
>
176+
<div className={classNames(styles.container, props.className)}>
177+
{isFetching ? (
178+
<div className={styles.loadingSpinnerContainer}>
179+
<LoadingSpinner className={styles.spinner} />
180+
</div>
181+
) : (
182+
<>
183+
{ssoUserLogins.length ? (
184+
<>
185+
{isTablet ? (
186+
<TableMobile
187+
columns={columnsMobile}
188+
data={ssoUserLogins}
189+
className={styles.mobileTable}
190+
/>
191+
) : (
192+
<Table
193+
columns={columns}
194+
data={ssoUserLogins}
195+
disableSorting
196+
onToggleSort={_.noop}
197+
className={styles.desktopTable}
198+
/>
199+
)}
200+
</>
201+
) : (
202+
<div>No SSO logins</div>
203+
)}
204+
</>
205+
)}
206+
207+
{showAddForm || showEditForm ? (
208+
<FormAddSSOLogin
209+
isAdding={isAdding}
210+
onSubmit={function onSubmit(data: FormAddSSOLoginData) {
211+
if (showAddForm) {
212+
doAddSSOUserLogin(data, cancelEditForm)
213+
} else {
214+
doUpdateSSOUserLogin(data, cancelEditForm)
215+
}
216+
}}
217+
onCancel={cancelEditForm}
218+
providers={props.providers}
219+
editingData={showEditForm}
220+
/>
221+
) : (
222+
<Button
223+
primary
224+
type='submit'
225+
onClick={function onClick() {
226+
setShowAddForm(true)
227+
}}
228+
>
229+
Add
230+
</Button>
231+
)}
232+
<div className={styles.actionButtons}>
233+
<Button
234+
secondary
235+
size='lg'
236+
onClick={handleClose}
237+
disabled={isLoading}
238+
>
239+
Close
240+
</Button>
241+
</div>
242+
243+
{(isAdding || isRemovingBool) && (
244+
<div className={styles.dialogLoadingSpinnerContainer}>
245+
<LoadingSpinner className={styles.spinner} />
246+
</div>
247+
)}
248+
</div>
249+
</BaseModal>
250+
)
251+
}
252+
253+
export default DialogEditUserSSOLogin
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as DialogEditUserSSOLogin } from './DialogEditUserSSOLogin'
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.container {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 20px;
5+
position: relative;
6+
width: 100%;
7+
}
8+
9+
.actionButtons {
10+
display: flex;
11+
justify-content: flex-end;
12+
gap: 6px;
13+
}

0 commit comments

Comments
 (0)