From 409cb3819eaae8c3b47ba21de8d7ea805760848f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Wed, 19 Jan 2022 23:59:10 +0100 Subject: [PATCH] Show birth date field on registration page when required MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/components/birth_date_input.js | 71 +++++++++++++++++++ .../components/registration_form.js | 24 ++++++- app/soapbox/features/edit_profile/index.js | 48 +++++-------- app/styles/forms.scss | 1 + package.json | 2 +- yarn.lock | 28 ++++---- 6 files changed, 127 insertions(+), 47 deletions(-) create mode 100644 app/soapbox/components/birth_date_input.js diff --git a/app/soapbox/components/birth_date_input.js b/app/soapbox/components/birth_date_input.js new file mode 100644 index 000000000..5183973d6 --- /dev/null +++ b/app/soapbox/components/birth_date_input.js @@ -0,0 +1,71 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import DatePicker from 'react-datepicker'; +import ImmutablePureComponent from 'react-immutable-pure-component'; +import { defineMessages, injectIntl } from 'react-intl'; +import { connect } from 'react-redux'; +import 'react-datepicker/dist/react-datepicker.css'; + +import { getFeatures } from 'soapbox/utils/features'; + +const messages = defineMessages({ + birthDatePlaceholder: { id: 'edit_profile.fields.birth_date_placeholder', defaultMessage: 'Your birth date' }, +}); + +const mapStateToProps = state => { + const features = getFeatures(state.get('instance')); + + return { + supportsBirthDates: features.birthDates, + minAge: state.getIn(['instance', 'pleroma', 'metadata', 'birth_date_min_age']), + }; +}; + +export default @connect(mapStateToProps) +@injectIntl +class EditProfile extends ImmutablePureComponent { + + static propTypes = { + hint: PropTypes.node, + required: PropTypes.bool, + supportsBirthDates: PropTypes.bool, + minAge: PropTypes.number, + onChange: PropTypes.func.isRequired, + value: PropTypes.instanceOf(Date), + }; + + isDateValid = date => { + const { minAge } = this.props; + const allowedDate = new Date(); + allowedDate.setDate(allowedDate.getDate() - minAge); + return date && allowedDate.setHours(0, 0, 0, 0) >= new Date(date).setHours(0, 0, 0, 0); + } + + render() { + const { intl, value, onChange, supportsBirthDates, hint, required } = this.props; + + if (!supportsBirthDates) return null; + + return ( +
+ {hint && ( +
+ {hint} +
+ )} +
+ +
+
+ ); + } + +} \ No newline at end of file diff --git a/app/soapbox/features/auth_login/components/registration_form.js b/app/soapbox/features/auth_login/components/registration_form.js index d9226f505..8b6ceb3a9 100644 --- a/app/soapbox/features/auth_login/components/registration_form.js +++ b/app/soapbox/features/auth_login/components/registration_form.js @@ -14,6 +14,7 @@ import { accountLookup } from 'soapbox/actions/accounts'; import { register, verifyCredentials } from 'soapbox/actions/auth'; import { openModal } from 'soapbox/actions/modal'; import { getSettings } from 'soapbox/actions/settings'; +import BirthDateInput from 'soapbox/components/birth_date_input'; import ShowablePassword from 'soapbox/components/showable_password'; import CaptchaField from 'soapbox/features/auth_login/components/captcha'; import { @@ -46,6 +47,7 @@ const mapStateToProps = (state, props) => ({ needsApproval: state.getIn(['instance', 'approval_required']), supportsEmailList: getFeatures(state.get('instance')).emailList, supportsAccountLookup: getFeatures(state.get('instance')).accountLookup, + birthDateRequired: state.getIn(['instance', 'pleroma', 'metadata', 'birth_date_required']), }); export default @connect(mapStateToProps) @@ -61,6 +63,7 @@ class RegistrationForm extends ImmutablePureComponent { supportsEmailList: PropTypes.bool, supportsAccountLookup: PropTypes.bool, inviteToken: PropTypes.string, + birthDateRequired: PropTypes.bool, } static contextTypes = { @@ -129,6 +132,12 @@ class RegistrationForm extends ImmutablePureComponent { this.setState({ passwordMismatch: !this.passwordsMatch() }); } + onBirthDateChange = birthDate => { + this.setState({ + birthDate, + }); + } + launchModal = () => { const { dispatch, intl, needsConfirmation, needsApproval } = this.props; @@ -197,6 +206,7 @@ class RegistrationForm extends ImmutablePureComponent { onSubmit = e => { const { dispatch, inviteToken } = this.props; + const { birthDate } = this.state; if (!this.passwordsMatch()) { this.setState({ passwordMismatch: true }); @@ -211,6 +221,10 @@ class RegistrationForm extends ImmutablePureComponent { if (inviteToken) { params.set('token', inviteToken); } + + if (birthDate) { + params.set('birth_date', birthDate.toISOString().slice(0, 10)); + } }); this.setState({ submissionLoading: true }); @@ -245,8 +259,8 @@ class RegistrationForm extends ImmutablePureComponent { } render() { - const { instance, intl, supportsEmailList } = this.props; - const { params, usernameUnavailable, passwordConfirmation, passwordMismatch } = this.state; + const { instance, intl, supportsEmailList, birthDateRequired } = this.props; + const { params, usernameUnavailable, passwordConfirmation, passwordMismatch, birthDate } = this.state; const isLoading = this.state.captchaLoading || this.state.submissionLoading; return ( @@ -311,6 +325,12 @@ class RegistrationForm extends ImmutablePureComponent { error={passwordMismatch === true} required /> + {!birthDateRequired && + } {instance.get('approval_required') && } diff --git a/app/soapbox/features/edit_profile/index.js b/app/soapbox/features/edit_profile/index.js index 0a3dad66b..f055d601e 100644 --- a/app/soapbox/features/edit_profile/index.js +++ b/app/soapbox/features/edit_profile/index.js @@ -5,17 +5,16 @@ import { import { unescape } from 'lodash'; import PropTypes from 'prop-types'; import React from 'react'; -import DatePicker from 'react-datepicker'; import ImmutablePropTypes from 'react-immutable-proptypes'; import ImmutablePureComponent from 'react-immutable-pure-component'; import { defineMessages, injectIntl, FormattedMessage } from 'react-intl'; import { connect } from 'react-redux'; -import 'react-datepicker/dist/react-datepicker.css'; import { updateNotificationSettings } from 'soapbox/actions/accounts'; import { patchMe } from 'soapbox/actions/me'; import snackbar from 'soapbox/actions/snackbar'; import { getSoapboxConfig } from 'soapbox/actions/soapbox'; +import BirthDateInput from 'soapbox/components/birth_date_input'; import Icon from 'soapbox/components/icon'; import { SimpleForm, @@ -69,7 +68,6 @@ const makeMapStateToProps = () => { verifiedCanEditName: soapbox.get('verifiedCanEditName'), supportsEmailList: features.emailList, supportsBirthDates: features.birthDates, - minAge: state.getIn(['instance', 'pleroma', 'metadata', 'birth_date_min_age']), }; }; @@ -102,7 +100,6 @@ class EditProfile extends ImmutablePureComponent { verifiedCanEditName: PropTypes.bool, supportsEmailList: PropTypes.bool, supportsBirthDates: PropTypes.bool, - minAge: PropTypes.number, }; state = { @@ -117,6 +114,7 @@ class EditProfile extends ImmutablePureComponent { const acceptsEmailList = account.getIn(['pleroma', 'accepts_email_list']); const discoverable = account.getIn(['source', 'pleroma', 'discoverable']); const birthDate = account.getIn(['pleroma', 'birth_date']); + const hideBirthDate = account.getIn(['pleroma', 'hide_birth_date']); const initialState = account.withMutations(map => { map.merge(map.get('source')); @@ -126,6 +124,7 @@ class EditProfile extends ImmutablePureComponent { map.set('accepts_email_list', acceptsEmailList); map.set('hide_network', hidesNetwork(account)); map.set('discoverable', discoverable); + map.set('hide_birth_date', hideBirthDate); if (birthDate) map.set('birthDate', new Date(birthDate)); unescapeParams(map, ['display_name', 'bio']); }); @@ -168,6 +167,7 @@ class EditProfile extends ImmutablePureComponent { hide_followers_count: state.hide_network, hide_follows_count: state.hide_network, birth_date: state.birthDate?.toISOString().slice(0, 10), + hide_birth_date: state.hide_birth_date, }, this.getFieldParams().toJS()); } @@ -235,7 +235,7 @@ class EditProfile extends ImmutablePureComponent { }; } - handleBirthDateChange = (birthDate) => { + handleBirthDateChange = birthDate => { this.setState({ birthDate, }); @@ -255,15 +255,8 @@ class EditProfile extends ImmutablePureComponent { }; } - isDateValid = date => { - const { minAge } = this.props; - const allowedDate = new Date(); - allowedDate.setDate(allowedDate.getDate() - minAge); - return date && allowedDate.setHours(0, 0, 0, 0) >= new Date(date).setHours(0, 0, 0, 0); - } - render() { - const { intl, maxFields, account, verifiedCanEditName, supportsEmailList, supportsBirthDates } = this.props; + const { intl, maxFields, account, verifiedCanEditName, supportsBirthDates, supportsEmailList } = this.props; const verified = isVerified(account); const canEditName = verifiedCanEditName || !verified; @@ -292,23 +285,11 @@ class EditProfile extends ImmutablePureComponent { onChange={this.handleTextChange} rows={3} /> - {supportsBirthDates && ( -
-
- -
-
- -
-
- )} + } + value={this.state.birthDate} + onChange={this.handleBirthDateChange} + />
@@ -363,6 +344,13 @@ class EditProfile extends ImmutablePureComponent { checked={this.state.discoverable} onChange={this.handleCheckboxChange} /> + {supportsBirthDates && } + hint={} + name='hide_birth_date' + checked={this.state.hide_birth_date} + onChange={this.handleCheckboxChange} + />} {supportsEmailList && } hint={} diff --git a/app/styles/forms.scss b/app/styles/forms.scss index 55daca384..9c46a965f 100644 --- a/app/styles/forms.scss +++ b/app/styles/forms.scss @@ -638,6 +638,7 @@ code { .datepicker { padding: 0; margin-bottom: 8px; + border: none; &__hint { padding-bottom: 0; diff --git a/package.json b/package.json index 72ad7dfb5..2706765e6 100644 --- a/package.json +++ b/package.json @@ -118,7 +118,7 @@ "qrcode.react": "^1.0.0", "react": "^16.13.1", "react-color": "^2.18.1", - "react-datepicker": "^4.1.1", + "react-datepicker": "^4.6.0", "react-dom": "^16.13.1", "react-helmet": "^6.0.0", "react-hotkeys": "^1.1.4", diff --git a/yarn.lock b/yarn.lock index 48f6a651c..53ae2f599 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3292,10 +3292,10 @@ data-urls@^2.0.0: whatwg-mimetype "^2.3.0" whatwg-url "^8.0.0" -date-fns@^2.0.1: - version "2.23.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.23.0.tgz#4e886c941659af0cf7b30fafdd1eaa37e88788a9" - integrity sha512-5ycpauovVyAk0kXNZz6ZoB9AYMZB4DObse7P3BPWmyEjXNORTI8EJ6X0uaSAq4sCHzM1uajzrkr6HnsLQpxGXA== +date-fns@^2.24.0: + version "2.28.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2" + integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw== debug@2.6.9, debug@^2.6.9: version "2.6.9" @@ -7820,16 +7820,16 @@ react-color@^2.18.1: reactcss "^1.2.0" tinycolor2 "^1.4.1" -react-datepicker@^4.1.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.2.1.tgz#72caf5055bc7c4eb0279c1f6d7624ded053edc4c" - integrity sha512-0gcvHMnX8rS1fV90PjjsB7MQdsWNU77JeVHf6bbwK9HnFxgwjVflTx40ebKmHV+leqe+f+FgUP9Nvqbe5RGyfA== +react-datepicker@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/react-datepicker/-/react-datepicker-4.6.0.tgz#10fc7c5b9c72df5c3e29712d559cb3fe73fd9f62" + integrity sha512-JGSQnQSQYUkS7zvSaZuyHv5lxp3wMrN7GXV0VA0E9Ax9fL3Bb6E1pSXjL6C3WoeuV8dt/mItQfRkPpRGCrl/OA== dependencies: "@popperjs/core" "^2.9.2" classnames "^2.2.6" - date-fns "^2.0.1" + date-fns "^2.24.0" prop-types "^15.7.2" - react-onclickoutside "^6.10.0" + react-onclickoutside "^6.12.0" react-popper "^2.2.5" react-dom@^16.13.1: @@ -7959,10 +7959,10 @@ react-notification@^6.8.4: dependencies: prop-types "^15.6.2" -react-onclickoutside@^6.10.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.0.tgz#c63db2e3c2c852b288160cdb6cff443604e28db4" - integrity sha512-oPlOTYcISLHfpMog2lUZMFSbqOs4LFcA4+vo7fpfevB5v9Z0D5VBDBkfeO5lv+hpEcGoaGk67braLT+QT+eICA== +react-onclickoutside@^6.12.0: + version "6.12.1" + resolved "https://registry.yarnpkg.com/react-onclickoutside/-/react-onclickoutside-6.12.1.tgz#92dddd28f55e483a1838c5c2930e051168c1e96b" + integrity sha512-a5Q7CkWznBRUWPmocCvE8b6lEYw1s6+opp/60dCunhO+G6E4tDTO2Sd2jKE+leEnnrLAE2Wj5DlDHNqj5wPv1Q== react-overlays@^0.9.0: version "0.9.3"