docbrown/app/javascript/onboarding/components/ProfileForm.jsx
Ridhwana 7a9629172d
Profile generalization for the onboarding workflow (#11629)
* feat: v1 of the profile form

* feat: fix inputs and create a handleChange event

* feat: update the submit values to represent values from the form

* feat: ensure that errors and success is handled on send

* refactor: remove unused code

* feat: replace the old profile form with a new one

* fix: use the renames file

* refactor: safety net for groups

* chore: fix code climate issue

* tests: amend + add tests

* test: fix broken spec

* feat: update the setup for the test

* tests: refactored them to move the fake response into the before all

* feat: clunky way to quickly cater for color field since it was a quick and easy implementation

* feat: add a field.description to the color field

* refactor: pull some duplicate code into a new function

* chore: cater for a textarea field

* overflow issue

* refactor: use FormField instead of manual divs

* Update spec/requests/users_onboarding_spec.rb

Co-authored-by: Vaidehi Joshi <vaidehi.sj@gmail.com>

* Update spec/requests/users_onboarding_spec.rb

Co-authored-by: Vaidehi Joshi <vaidehi.sj@gmail.com>

* refactor: move out of method and into a constant

* refactor: user_onboarding_update to be more readable

* refactor: use current.save instead of declaring a new variable

* refactor: remove explicit check for bookean

* refactor: use request from '@utilities/http' instead :)

* refactor: no need for if with destructured groups

* refactor: move color field to its own component

* chore: remove colorfield now thats its a component

* fix: forgot about field

* feat: a text area component

* chore: remove textField function in favor of component

* refactor: checkbox refactor

* refactor: add a switch statement

* chore: move into a more organized folder

* fix: move the handler to props and off from field

* fix: add a conditional so we dont hit an error undefined method `success?' for nil:NilClass

* refactor: change from component to function

* feat: add a key attribute

* refactor: add role for readers

* refactor: remove unnecessary guard clauses

* refactor: use function instead of an arrow

* chore: document components

* test

* padding

* fixes

* test: fix the tests by using more general matchers

Co-authored-by: Paweł Ludwiczak <ludwiczakpawel@gmail.com>
Co-authored-by: Vaidehi Joshi <vaidehi.sj@gmail.com>
2020-12-08 19:53:00 +02:00

236 lines
6.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { h, Component } from 'preact';
import PropTypes from 'prop-types';
import { userData, getContentOfToken, updateOnboarding } from '../utilities';
import Navigation from './Navigation';
import ColorPicker from './ProfileForm/ColorPicker';
import TextArea from './ProfileForm/TextArea';
import TextInput from './ProfileForm/TextInput';
import CheckBox from './ProfileForm/CheckBox';
import { request } from '@utilities/http';
/* eslint-disable camelcase */
class ProfileForm extends Component {
constructor(props) {
super(props);
this.handleFieldChange = this.handleFieldChange.bind(this);
this.handleColorPickerChange = this.handleColorPickerChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.user = userData();
this.state = {
groups: [],
formValues: {},
canSkip: true,
last_onboarding_page: 'v2: personal info form',
};
}
componentDidMount() {
this.getProfielFieldGroups();
updateOnboarding('v2: personal info form');
}
async getProfielFieldGroups() {
try {
const response = await request(`/profile_field_groups?onboarding=true`);
if (response.ok) {
const data = await response.json();
this.setState({ groups: data.profile_field_groups });
} else {
throw new Error(response.statusText);
}
} catch (error) {
this.setState({ error: true, errorMessage: error.toString() });
}
}
onSubmit() {
const csrfToken = getContentOfToken('csrf-token');
const { formValues, last_onboarding_page } = this.state;
fetch('/onboarding_update', {
method: 'PATCH',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json',
},
body: JSON.stringify({
user: { last_onboarding_page },
profile: { ...formValues },
}),
credentials: 'same-origin',
}).then((response) => {
if (response.ok) {
const { next } = this.props;
next();
}
});
}
handleFieldChange(e) {
const { formValues } = { ...this.state };
const currentFormState = formValues;
const { name, value } = e.target;
currentFormState[name] = value;
this.setState({
formValues: currentFormState,
canSkip: this.formIsEmpty(currentFormState),
});
}
handleColorPickerChange(e) {
const { formValues } = { ...this.state };
const currentFormState = formValues;
const field = e.target;
const { name, value } = field;
let sibling = field.nextElementSibling
? field.nextElementSibling
: field.previousElementSibling;
sibling.value = value;
currentFormState[name] = value;
this.setState({
formValues: currentFormState,
canSkip: this.formIsEmpty(currentFormState),
});
}
formIsEmpty(currentFormState) {
// Once we've derived the new form values, check if the form is empty
// and use that value to set the `canSkip` property on the state.
Object.values(currentFormState).filter((v) => v.length > 0).length === 0;
}
renderAppropriateFieldType(field) {
switch (field.input_type) {
case 'check_box':
return (
<CheckBox
key={field.id}
field={field}
onFieldChange={this.handleFieldChange}
/>
);
case 'color_field':
return (
<ColorPicker
key={field.id}
field={field}
onColorChange={this.handleColorPickerChange}
/>
);
case 'text_area':
return (
<TextArea
key={field.id}
field={field}
onFieldChange={this.handleFieldChange}
/>
);
default:
return (
<TextInput
key={field.id}
field={field}
onFieldChange={this.handleFieldChange}
/>
);
}
}
render() {
const {
prev,
slidesCount,
currentSlideIndex,
communityConfig,
} = this.props;
const { profile_image_90, username, name } = this.user;
const { canSkip, groups = [], error, errorMessage } = this.state;
if (error) {
return (
<div role="alert" class="crayons-notice crayons-notice--danger">
An error occurred: {errorMessage}
</div>
);
}
const sections = groups.map((group) => {
return (
<div key={group.id} class="onboarding-profile-sub-section">
<h2>{group.name}</h2>
{group.description && (
<div class="color-base-60">{group.description})</div>
)}
<div>
{group.profile_fields.map((field) => {
return this.renderAppropriateFieldType(field);
})}
</div>
</div>
);
});
return (
<div
data-testid="onboarding-profile-form"
className="onboarding-main crayons-modal"
>
<div className="crayons-modal__box">
<Navigation
prev={prev}
next={this.onSubmit}
canSkip={canSkip}
slidesCount={slidesCount}
currentSlideIndex={currentSlideIndex}
/>
<div className="onboarding-content about">
<header className="onboarding-content-header">
<h1 className="title">Build your profile</h1>
<h2
data-testid="onboarding-profile-subtitle"
className="subtitle"
>
Tell us a little bit about yourself this is how others will
see you on {communityConfig.communityName}. Youll always be
able to edit this later in your Settings.
</h2>
</header>
<div className="current-user-info">
<figure className="current-user-avatar-container">
<img
className="current-user-avatar"
alt="profile"
src={profile_image_90}
/>
</figure>
<h3>{name}</h3>
<p>{username}</p>
</div>
<div>{sections}</div>
</div>
</div>
</div>
);
}
}
ProfileForm.propTypes = {
prev: PropTypes.func.isRequired,
next: PropTypes.func.isRequired,
slidesCount: PropTypes.number.isRequired,
currentSlideIndex: PropTypes.func.isRequired,
communityConfig: PropTypes.shape({
communityName: PropTypes.string.isRequired,
}),
};
export default ProfileForm;
/* eslint-enable camelcase */