|
3 | 3 | * @module components/theme/PasswordReset/PasswordReset |
4 | 4 | */ |
5 | 5 |
|
6 | | -import React, { Component } from 'react'; |
7 | | -import PropTypes from 'prop-types'; |
8 | | -import { connect } from 'react-redux'; |
9 | | -import { compose } from 'redux'; |
10 | | -import { Link, withRouter } from 'react-router-dom'; |
| 6 | +import { useState, useEffect } from 'react'; |
| 7 | +import { useSelector, useDispatch } from 'react-redux'; |
| 8 | +import { Link, useHistory, useParams } from 'react-router-dom'; |
11 | 9 | import Helmet from '@plone/volto/helpers/Helmet/Helmet'; |
12 | 10 | import { Container } from 'semantic-ui-react'; |
13 | | -import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; |
| 11 | +import { FormattedMessage, defineMessages, useIntl } from 'react-intl'; |
14 | 12 |
|
15 | 13 | import { Form } from '@plone/volto/components/manage/Form'; |
16 | 14 | import { setInitialPassword } from '@plone/volto/actions/users/users'; |
@@ -90,220 +88,139 @@ const messages = defineMessages({ |
90 | 88 | }); |
91 | 89 |
|
92 | 90 | /** |
93 | | - * PasswordReset class. |
94 | | - * @class PasswordReset |
95 | | - * @extends Component |
| 91 | + * @function PasswordReset |
| 92 | + * @returns {JSX.Element} |
96 | 93 | */ |
97 | | -class PasswordReset extends Component { |
98 | | - /** |
99 | | - * Property types. |
100 | | - * @property {Object} propTypes Property types. |
101 | | - * @static |
102 | | - */ |
103 | | - static propTypes = { |
104 | | - loading: PropTypes.bool.isRequired, |
105 | | - loaded: PropTypes.bool.isRequired, |
106 | | - error: PropTypes.string, |
107 | | - token: PropTypes.string.isRequired, |
108 | | - setInitialPassword: PropTypes.func.isRequired, |
109 | | - }; |
| 94 | +function PasswordReset() { |
| 95 | + const dispatch = useDispatch(); |
| 96 | + const history = useHistory(); |
| 97 | + const { token } = useParams(); |
110 | 98 |
|
111 | | - /** |
112 | | - * Default properties. |
113 | | - * @property {Object} defaultProps Default properties. |
114 | | - * @static |
115 | | - */ |
116 | | - static defaultProps = { |
117 | | - error: null, |
118 | | - }; |
| 99 | + const loading = useSelector((state) => state.users.initial.loading); |
| 100 | + const loaded = useSelector((state) => state.users.initial.loaded); |
| 101 | + const error = useSelector((state) => state.users.initial.error); |
119 | 102 |
|
120 | | - /** |
121 | | - * Constructor |
122 | | - * @method constructor |
123 | | - * @param {Object} props Component properties |
124 | | - * @constructs Controlpanel |
125 | | - */ |
126 | | - constructor(props) { |
127 | | - super(props); |
128 | | - this.onCancel = this.onCancel.bind(this); |
129 | | - this.onSubmit = this.onSubmit.bind(this); |
130 | | - this.state = { |
131 | | - error: null, |
132 | | - isSuccessful: false, |
133 | | - }; |
| 103 | + const [localError, setLocalError] = useState(null); |
| 104 | + const [isSuccessful, setIsSuccessful] = useState(false); |
| 105 | + const intl = useIntl(); |
134 | 106 |
|
135 | | - this.identifierField = config.settings.useEmailAsLogin |
136 | | - ? 'email' |
137 | | - : 'username'; |
| 107 | + const identifierField = config.settings.useEmailAsLogin |
| 108 | + ? 'email' |
| 109 | + : 'username'; |
138 | 110 |
|
139 | | - this.identifierTitle = |
140 | | - this.identifierField === 'email' |
141 | | - ? this.props.intl.formatMessage(messages.emailTitle) |
142 | | - : this.props.intl.formatMessage(messages.usernameTitle); |
| 111 | + const identifierTitle = |
| 112 | + identifierField === 'email' |
| 113 | + ? intl.formatMessage(messages.emailTitle) |
| 114 | + : intl.formatMessage(messages.usernameTitle); |
143 | 115 |
|
144 | | - this.identifierDescription = |
145 | | - this.identifierField === 'email' |
146 | | - ? this.props.intl.formatMessage(messages.emailDescription) |
147 | | - : this.props.intl.formatMessage(messages.usernameDescription); |
148 | | - } |
| 116 | + const identifierDescription = |
| 117 | + identifierField === 'email' |
| 118 | + ? intl.formatMessage(messages.emailDescription) |
| 119 | + : intl.formatMessage(messages.usernameDescription); |
149 | 120 |
|
150 | | - /** |
151 | | - * Component will receive props |
152 | | - * @method componentWillReceiveProps |
153 | | - * @param {Object} nextProps Next properties |
154 | | - * @returns {undefined} |
155 | | - */ |
156 | | - UNSAFE_componentWillReceiveProps(nextProps) { |
157 | | - if (this.props.loading && nextProps.loaded) { |
158 | | - this.setState({ isSuccessful: true }); |
| 121 | + useEffect(() => { |
| 122 | + if (!loading && loaded) { |
| 123 | + setIsSuccessful(true); |
159 | 124 | } |
160 | | - } |
| 125 | + }, [loading, loaded]); |
161 | 126 |
|
162 | 127 | /** |
163 | 128 | * Submit handler |
164 | 129 | * @method onSubmit |
165 | 130 | * @param {object} data Form data. |
166 | | - * @param {object} event Form data. |
167 | 131 | * @returns {undefined} |
168 | 132 | */ |
169 | | - onSubmit(data) { |
| 133 | + const onSubmit = (data) => { |
170 | 134 | if (data.password === data.passwordRepeat) { |
171 | | - this.props.setInitialPassword( |
172 | | - data[this.identifierField], |
173 | | - this.props.token, |
174 | | - data.password, |
175 | | - ); |
176 | | - this.setState({ |
177 | | - error: null, |
178 | | - }); |
| 135 | + dispatch(setInitialPassword(data[identifierField], token, data.password)); |
| 136 | + setLocalError(null); |
179 | 137 | } else { |
180 | | - this.setState({ |
181 | | - error: { |
182 | | - message: this.props.intl.formatMessage(messages.passwordsDoNotMatch), |
183 | | - }, |
| 138 | + setLocalError({ |
| 139 | + message: intl.formatMessage(messages.passwordsDoNotMatch), |
184 | 140 | }); |
185 | 141 | } |
186 | | - } |
| 142 | + }; |
187 | 143 |
|
188 | 144 | /** |
189 | 145 | * Cancel handler |
190 | 146 | * @method onCancel |
191 | 147 | * @returns {undefined} |
192 | 148 | */ |
193 | | - onCancel() { |
194 | | - this.props.history.goBack(); |
| 149 | + const onCancel = () => { |
| 150 | + history.goBack(); |
| 151 | + }; |
| 152 | + |
| 153 | + if (isSuccessful) { |
| 154 | + return ( |
| 155 | + <Container> |
| 156 | + <h1 className="documentFirstHeading"> |
| 157 | + <FormattedMessage {...messages.successRedirectToLoginTitle} /> |
| 158 | + </h1> |
| 159 | + <p className="description"> |
| 160 | + <FormattedMessage |
| 161 | + {...messages.successRedirectToLoginBody} |
| 162 | + values={{ |
| 163 | + link: ( |
| 164 | + <Link to="/login">{intl.formatMessage({ id: 'Log In' })}</Link> |
| 165 | + ), |
| 166 | + }} |
| 167 | + /> |
| 168 | + </p> |
| 169 | + </Container> |
| 170 | + ); |
195 | 171 | } |
196 | 172 |
|
197 | | - /** |
198 | | - * Render method. |
199 | | - * @method render |
200 | | - * @returns {string} Markup for the component. |
201 | | - */ |
202 | | - render() { |
203 | | - if (this.state.isSuccessful) { |
204 | | - return ( |
| 173 | + if (token) { |
| 174 | + const errmsg = error ? error.response?.body?.error || error : null; |
| 175 | + return ( |
| 176 | + <div id="page-password-reset"> |
| 177 | + <Helmet title={intl.formatMessage(messages.passwordReset)} /> |
205 | 178 | <Container> |
206 | | - <h1 className="documentFirstHeading"> |
207 | | - <FormattedMessage |
208 | | - id="Account activation completed" |
209 | | - defaultMessage="Account activation completed" |
210 | | - /> |
211 | | - </h1> |
212 | | - <p className="description"> |
213 | | - <FormattedMessage |
214 | | - id="Your password has been set successfully. You may now {link} with your new password." |
215 | | - defaultMessage="Your password has been set successfully. You may now {link} with your new password." |
216 | | - values={{ |
217 | | - link: ( |
218 | | - <Link to="/login"> |
219 | | - {this.props.intl.formatMessage({ id: 'Log In' })} |
220 | | - </Link> |
221 | | - ), |
222 | | - }} |
223 | | - /> |
224 | | - </p> |
225 | | - </Container> |
226 | | - ); |
227 | | - } |
228 | | - if (this.props.token) { |
229 | | - const errmsg = this.props.error |
230 | | - ? this.props.error.response.body.error |
231 | | - : null; |
232 | | - return ( |
233 | | - <div id="page-password-reset"> |
234 | | - <Helmet |
235 | | - title={this.props.intl.formatMessage(messages.passwordReset)} |
236 | | - /> |
237 | | - <Container> |
238 | | - <Form |
239 | | - title={this.props.intl.formatMessage(messages.title)} |
240 | | - description={this.props.intl.formatMessage(messages.description)} |
241 | | - onSubmit={this.onSubmit} |
242 | | - onCancel={this.onCancel} |
243 | | - error={this.state.error || errmsg} |
244 | | - schema={{ |
245 | | - fieldsets: [ |
246 | | - { |
247 | | - id: 'default', |
248 | | - title: this.props.intl.formatMessage(messages.default), |
249 | | - fields: [ |
250 | | - this.identifierField, |
251 | | - 'password', |
252 | | - 'passwordRepeat', |
253 | | - ], |
254 | | - }, |
255 | | - ], |
256 | | - properties: { |
257 | | - [this.identifierField]: { |
258 | | - type: 'string', |
259 | | - title: this.identifierTitle, |
260 | | - description: this.identifierDescription, |
261 | | - }, |
262 | | - password: { |
263 | | - description: this.props.intl.formatMessage( |
264 | | - messages.passwordDescription, |
265 | | - ), |
266 | | - title: this.props.intl.formatMessage( |
267 | | - messages.passwordTitle, |
268 | | - ), |
269 | | - type: 'string', |
270 | | - widget: 'password', |
271 | | - }, |
272 | | - passwordRepeat: { |
273 | | - description: this.props.intl.formatMessage( |
274 | | - messages.passwordRepeatDescription, |
275 | | - ), |
276 | | - title: this.props.intl.formatMessage( |
277 | | - messages.passwordRepeatTitle, |
278 | | - ), |
279 | | - type: 'string', |
280 | | - widget: 'password', |
281 | | - }, |
| 179 | + <Form |
| 180 | + title={intl.formatMessage(messages.title)} |
| 181 | + description={intl.formatMessage(messages.description)} |
| 182 | + onSubmit={onSubmit} |
| 183 | + onCancel={onCancel} |
| 184 | + error={localError || errmsg} |
| 185 | + schema={{ |
| 186 | + fieldsets: [ |
| 187 | + { |
| 188 | + id: 'default', |
| 189 | + title: intl.formatMessage(messages.default), |
| 190 | + fields: [identifierField, 'password', 'passwordRepeat'], |
282 | 191 | }, |
283 | | - submitLabel: this.props.intl.formatMessage( |
284 | | - messages.setMyPassword, |
285 | | - ), |
286 | | - required: [this.identifierField, 'password', 'passwordRepeat'], |
287 | | - }} |
288 | | - /> |
289 | | - </Container> |
290 | | - </div> |
291 | | - ); |
292 | | - } |
293 | | - return <div />; |
| 192 | + ], |
| 193 | + properties: { |
| 194 | + [identifierField]: { |
| 195 | + type: 'string', |
| 196 | + title: identifierTitle, |
| 197 | + description: identifierDescription, |
| 198 | + }, |
| 199 | + password: { |
| 200 | + description: intl.formatMessage(messages.passwordDescription), |
| 201 | + title: intl.formatMessage(messages.passwordTitle), |
| 202 | + type: 'string', |
| 203 | + widget: 'password', |
| 204 | + }, |
| 205 | + passwordRepeat: { |
| 206 | + description: intl.formatMessage( |
| 207 | + messages.passwordRepeatDescription, |
| 208 | + ), |
| 209 | + title: intl.formatMessage(messages.passwordRepeatTitle), |
| 210 | + type: 'string', |
| 211 | + widget: 'password', |
| 212 | + }, |
| 213 | + }, |
| 214 | + submitLabel: intl.formatMessage(messages.setMyPassword), |
| 215 | + required: [identifierField, 'password', 'passwordRepeat'], |
| 216 | + }} |
| 217 | + /> |
| 218 | + </Container> |
| 219 | + </div> |
| 220 | + ); |
294 | 221 | } |
| 222 | + |
| 223 | + return <div />; |
295 | 224 | } |
296 | 225 |
|
297 | | -export default compose( |
298 | | - withRouter, |
299 | | - injectIntl, |
300 | | - connect( |
301 | | - (state, props) => ({ |
302 | | - loading: state.users.initial.loading, |
303 | | - loaded: state.users.initial.loaded, |
304 | | - error: state.users.initial.error, |
305 | | - token: props.match.params.token, |
306 | | - }), |
307 | | - { setInitialPassword }, |
308 | | - ), |
309 | | -)(PasswordReset); |
| 226 | +export default PasswordReset; |
0 commit comments