Skip to content

Commit d1523a0

Browse files
authored
chore(ui): convert user-info-overview to function component (argoproj#23786)
Signed-off-by: linghaoSu <linghao.su@daocloud.io>
1 parent 65cbbca commit d1523a0

File tree

1 file changed

+180
-155
lines changed

1 file changed

+180
-155
lines changed
Lines changed: 180 additions & 155 deletions
Original file line numberDiff line numberDiff line change
@@ -1,172 +1,197 @@
1-
import * as React from 'react';
1+
import React, {useCallback, useContext, useRef, useState} from 'react';
22

33
import {FormField, NotificationType, SlidingPanel} from 'argo-ui/src/index';
4-
import * as PropTypes from 'prop-types';
54
import {Form, FormApi, FormValue, Nested, Text} from 'react-form';
6-
import {RouteComponentProps} from 'react-router';
75
import {DataLoader, ErrorNotification, Page, Spinner} from '../../../shared/components';
8-
import {AppContext} from '../../../shared/context';
6+
import {Context} from '../../../shared/context';
97
import {services} from '../../../shared/services';
108

119
import './user-info-overview.scss';
10+
import {UserInfo} from '../../../shared/models';
1211

13-
export class UserInfoOverview extends React.Component<RouteComponentProps<any>, {connecting: boolean}> {
14-
public static contextTypes = {
15-
router: PropTypes.object,
16-
apis: PropTypes.object,
17-
history: PropTypes.object
18-
};
12+
// Constants
13+
const CHANGE_PASSWORD_PARAM = 'changePassword';
14+
const ARGOCD_ISSUER = 'argocd';
1915

20-
private formApiPassword: FormApi;
16+
// Types
17+
interface PasswordFormData {
18+
currentPassword: string;
19+
newPassword: string;
20+
confirmNewPassword: string;
21+
}
2122

22-
constructor(props: RouteComponentProps<any>) {
23-
super(props);
24-
this.state = {connecting: false};
25-
}
23+
// Password form validation
24+
const validatePasswordForm = (params: PasswordFormData) => ({
25+
currentPassword: !params.currentPassword && 'Current password is required.',
26+
newPassword: (!params.newPassword && 'New password is required.') || (params.newPassword !== params.confirmNewPassword && 'Passwords do not match.'),
27+
confirmNewPassword: (!params.confirmNewPassword || params.confirmNewPassword !== params.newPassword) && 'Please confirm your new password.'
28+
});
2629

27-
public render() {
28-
return (
29-
<DataLoader key='userInfo' load={() => services.users.get()}>
30-
{userInfo => (
31-
<Page
32-
title='User Info'
33-
toolbar={{
34-
breadcrumbs: [{title: 'User Info'}],
35-
actionMenu:
36-
userInfo.loggedIn && userInfo.iss === 'argocd'
37-
? {
38-
items: [
39-
{
40-
iconClassName: 'fa fa-lock',
41-
title: 'Update Password',
42-
action: () => (this.showChangePassword = true)
43-
}
44-
]
45-
}
46-
: {items: []}
47-
}}>
48-
<div>
49-
<div className='user-info'>
50-
<div className='argo-container'>
51-
<div className='user-info-overview__panel white-box'>
52-
{userInfo.loggedIn ? (
53-
<React.Fragment key='userInfoInner'>
54-
<p key='username'>Username: {userInfo.username}</p>
55-
<p key='iss'>Issuer: {userInfo.iss}</p>
56-
{userInfo.groups && (
57-
<React.Fragment key='userInfo4'>
58-
<p>Groups:</p>
59-
<ul>
60-
{userInfo.groups.map(group => (
61-
<li key={group}>{group}</li>
62-
))}
63-
</ul>
64-
</React.Fragment>
65-
)}
66-
</React.Fragment>
67-
) : (
68-
<p key='loggedOutMessage'>You are not logged in</p>
69-
)}
70-
</div>
71-
</div>
72-
</div>
73-
{userInfo.loggedIn && userInfo.iss === 'argocd' ? (
74-
<SlidingPanel
75-
isShown={this.showChangePassword}
76-
onClose={() => (this.showChangePassword = false)}
77-
header={
78-
<div>
79-
<button
80-
className='argo-button argo-button--base'
81-
onClick={() => {
82-
this.formApiPassword.submitForm(null);
83-
}}>
84-
<Spinner show={this.state.connecting} style={{marginRight: '5px'}} />
85-
Save New Password
86-
</button>{' '}
87-
<button onClick={() => (this.showChangePassword = false)} className='argo-button argo-button--base-o'>
88-
Cancel
89-
</button>
90-
</div>
91-
}>
92-
<h4>Update account password</h4>
93-
<Form
94-
onSubmit={params => this.changePassword(userInfo.username, params.currentPassword, params.newPassword)}
95-
getApi={api => (this.formApiPassword = api)}
96-
defaultValues={{type: 'git'}}
97-
validateError={(params: {currentPassword: string; newPassword: string; confirmNewPassword: string}) => ({
98-
currentPassword: !params.currentPassword && 'Current password is required.',
99-
newPassword:
100-
(!params.newPassword && 'New password is required.') ||
101-
(params.newPassword !== params.confirmNewPassword && 'Confirm your new password.'),
102-
confirmNewPassword: (!params.confirmNewPassword || params.confirmNewPassword !== params.newPassword) && 'Confirm your new password.'
103-
})}>
104-
{formApi => (
105-
<form onSubmit={formApi.submitForm} role='form' className='change-password width-control'>
106-
<div className='argo-form-row'>
107-
<FormField
108-
formApi={formApi}
109-
label='Current Password'
110-
field='currentPassword'
111-
component={Text}
112-
componentProps={{type: 'password'}}
113-
/>
114-
</div>
115-
<div className='argo-form-row'>
116-
<FormField formApi={formApi} label='New Password' field='newPassword' component={Text} componentProps={{type: 'password'}} />
117-
</div>
118-
<div className='argo-form-row'>
119-
<FormField
120-
formApi={formApi}
121-
label='Confirm New Password'
122-
field='confirmNewPassword'
123-
component={Text}
124-
componentProps={{type: 'password'}}
125-
/>
126-
</div>
127-
</form>
128-
)}
129-
</Form>
130-
</SlidingPanel>
131-
) : (
132-
<div />
133-
)}
134-
</div>
135-
</Page>
136-
)}
137-
</DataLoader>
138-
);
139-
}
30+
export const UserInfoComponent = ({userInfo}: {userInfo: UserInfo}) => {
31+
const appContext = useContext(Context);
32+
33+
const [isConnecting, setIsConnecting] = useState(false);
34+
const [showChangePassword, setShowChangePassword] = useState(new URLSearchParams(appContext.history.location.search).get(CHANGE_PASSWORD_PARAM) === 'true');
35+
36+
const formApiPassword = useRef<FormApi>(null);
37+
38+
const changePassword = useCallback(
39+
async (username: string, currentPassword: Nested<FormValue> | FormValue, newPassword: Nested<FormValue> | FormValue) => {
40+
setIsConnecting(true);
41+
try {
42+
await services.accounts.changePassword(username, currentPassword, newPassword);
43+
appContext.notifications.show({
44+
type: NotificationType.Success,
45+
content: 'Your password has been successfully updated.'
46+
});
47+
setShowChangePassword(false);
48+
// Clear the URL parameter
49+
appContext.history.push(appContext.history.location.pathname);
50+
} catch (e) {
51+
appContext.notifications.show({
52+
content: <ErrorNotification title='Unable to update your password.' e={e} />,
53+
type: NotificationType.Error
54+
});
55+
} finally {
56+
setIsConnecting(false);
57+
}
58+
},
59+
[appContext.notifications, appContext.history]
60+
);
14061

141-
private async changePassword(username: string, currentPassword: Nested<FormValue> | FormValue, newPassword: Nested<FormValue> | FormValue) {
142-
try {
143-
await services.accounts.changePassword(username, currentPassword, newPassword);
144-
this.appContext.apis.notifications.show({type: NotificationType.Success, content: 'Your password has been successfully updated.'});
145-
this.showChangePassword = false;
146-
} catch (e) {
147-
this.appContext.apis.notifications.show({
148-
content: <ErrorNotification title='Unable to update your password.' e={e} />,
149-
type: NotificationType.Error
150-
});
151-
}
152-
}
62+
const clearChangePasswordForm = useCallback(() => {
63+
formApiPassword.current?.resetAll();
64+
}, []);
15365

154-
// Whether to show the HTTPS repository connection dialogue on the page
155-
private get showChangePassword() {
156-
return new URLSearchParams(this.props.location.search).get('changePassword') === 'true';
157-
}
66+
const updateShowChangePassword = useCallback(
67+
(val: boolean) => {
68+
setShowChangePassword(val);
69+
clearChangePasswordForm();
70+
const url = `${appContext.history.location.pathname}${val ? `?${CHANGE_PASSWORD_PARAM}=true` : ''}`;
71+
appContext.history.push(url);
72+
},
73+
[appContext.history, clearChangePasswordForm]
74+
);
15875

159-
private set showChangePassword(val: boolean) {
160-
this.clearChangePasswordForm();
161-
this.appContext.router.history.push(`${this.props.match.url}?changePassword=${val}`);
162-
}
76+
const handleFormSubmit = useCallback(() => {
77+
formApiPassword.current?.submitForm(null);
78+
}, []);
16379

164-
// Empty all fields in HTTPS repository form
165-
private clearChangePasswordForm() {
166-
this.formApiPassword.resetAll();
167-
}
80+
const isPasswordChangeAvailable = userInfo.loggedIn && userInfo.iss === ARGOCD_ISSUER;
81+
82+
return (
83+
<Page
84+
title='User Info'
85+
toolbar={{
86+
breadcrumbs: [{title: 'User Info'}],
87+
actionMenu: isPasswordChangeAvailable
88+
? {
89+
items: [
90+
{
91+
iconClassName: 'fa fa-lock',
92+
title: 'Update Password',
93+
action: () => updateShowChangePassword(true)
94+
}
95+
]
96+
}
97+
: {items: []}
98+
}}>
99+
<div>
100+
<div className='user-info'>
101+
<div className='argo-container'>
102+
<div className='user-info-overview__panel white-box'>
103+
{userInfo.loggedIn ? (
104+
<>
105+
<p>Username: {userInfo.username}</p>
106+
<p>Issuer: {userInfo.iss}</p>
107+
{userInfo.groups && userInfo.groups.length > 0 && (
108+
<>
109+
<p>Groups:</p>
110+
<ul>
111+
{userInfo.groups.map(group => (
112+
<li key={group}>{group}</li>
113+
))}
114+
</ul>
115+
</>
116+
)}
117+
</>
118+
) : (
119+
<p>You are not logged in</p>
120+
)}
121+
</div>
122+
</div>
123+
</div>
124+
{isPasswordChangeAvailable && (
125+
<SlidingPanel
126+
isShown={showChangePassword}
127+
onClose={() => updateShowChangePassword(false)}
128+
header={
129+
<div>
130+
<button className='argo-button argo-button--base' onClick={handleFormSubmit} disabled={isConnecting}>
131+
<Spinner show={isConnecting} style={{marginRight: '5px'}} />
132+
Save New Password
133+
</button>{' '}
134+
<button onClick={() => updateShowChangePassword(false)} className='argo-button argo-button--base-o' disabled={isConnecting}>
135+
Cancel
136+
</button>
137+
</div>
138+
}>
139+
<h4>Update account password</h4>
140+
<Form
141+
onSubmit={(params: PasswordFormData) => changePassword(userInfo.username, params.currentPassword, params.newPassword)}
142+
getApi={api => (formApiPassword.current = api)}
143+
validateError={validatePasswordForm}>
144+
{formApi => (
145+
<form onSubmit={formApi.submitForm} role='form' className='change-password width-control'>
146+
<div className='argo-form-row'>
147+
<FormField
148+
formApi={formApi}
149+
label='Current Password'
150+
field='currentPassword'
151+
component={Text}
152+
componentProps={{
153+
type: 'password',
154+
autoComplete: 'current-password',
155+
disabled: isConnecting
156+
}}
157+
/>
158+
</div>
159+
<div className='argo-form-row'>
160+
<FormField
161+
formApi={formApi}
162+
label='New Password'
163+
field='newPassword'
164+
component={Text}
165+
componentProps={{
166+
type: 'password',
167+
autoComplete: 'new-password',
168+
disabled: isConnecting
169+
}}
170+
/>
171+
</div>
172+
<div className='argo-form-row'>
173+
<FormField
174+
formApi={formApi}
175+
label='Confirm New Password'
176+
field='confirmNewPassword'
177+
component={Text}
178+
componentProps={{
179+
type: 'password',
180+
autoComplete: 'new-password',
181+
disabled: isConnecting
182+
}}
183+
/>
184+
</div>
185+
</form>
186+
)}
187+
</Form>
188+
</SlidingPanel>
189+
)}
190+
</div>
191+
</Page>
192+
);
193+
};
168194

169-
private get appContext(): AppContext {
170-
return this.context as AppContext;
171-
}
195+
export function UserInfoOverview() {
196+
return <DataLoader load={() => services.users.get()}>{userInfo => <UserInfoComponent userInfo={userInfo} />}</DataLoader>;
172197
}

0 commit comments

Comments
 (0)