Skip to content
This repository has been archived by the owner on Jul 18, 2024. It is now read-only.

fix: font styling and small ux bugs #373

Merged
merged 4 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/library-authoring/common/FormGroup.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@ const FormGroup = (props) => {
</Form.Control>

{props.helpText && !props.errorMessage && (
<Form.Control.Feedback type="default" key="help-text">
<Form.Control.Feedback type="default" key="help-text" className="x-small">
{props.helpText}
</Form.Control.Feedback>
)}

{props.errorMessage && (
<Form.Control.Feedback type="invalid" key="error" feedback-for={props.name}>
<Form.Control.Feedback
type="invalid"
key="error"
feedback-for={props.name}
>
{props.errorMessage}
</Form.Control.Feedback>
)}
Expand Down
13 changes: 13 additions & 0 deletions src/library-authoring/common/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,16 @@
font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
}
}
.library-create-wrapper {
.pgn__form-group {
.pgn__form-control-decorator-group {
margin: 0
}
}
.pgn__form-text-invalid {
margin-left: -16px;
span.pgn__icon {
visibility: hidden;
}
}
}
168 changes: 87 additions & 81 deletions src/library-authoring/create-library/LibraryCreatePage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
Alert,
AlertModal,
ActionRow,
Card,
} from '@edx/paragon';
import { Info } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
Expand Down Expand Up @@ -207,11 +208,10 @@ export class LibraryCreatePage extends React.Component {
const { intl } = this.props;
const { errors } = this.state;
const slugRegex = new RegExp(VALID_SLUG_ID_REGEX);

switch (fieldName) {
case 'org':
if (!value) {
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty']);
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty.org']);
} else if (value && !data.org) {
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.mismatch.org']);
} else {
Expand All @@ -220,7 +220,7 @@ export class LibraryCreatePage extends React.Component {
break;
case 'slug':
if (!value) {
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty']);
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty.slug']);
} else if (value && !value.match(slugRegex)) {
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.invalid.slug']);
} else {
Expand All @@ -229,7 +229,7 @@ export class LibraryCreatePage extends React.Component {
break;
default:
if (!value) {
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty']);
errors[fieldName] = intl.formatMessage(messages['library.form.field.error.empty.title']);
} else {
errors[fieldName] = '';
}
Expand All @@ -256,7 +256,7 @@ export class LibraryCreatePage extends React.Component {
<Alert
icon={Info}
variant="danger"
className="form-create-alert"
className="form-create-alert mt-3 mb-4.5"
>
{errorTitle && <Alert.Heading>{errorTitle}</Alert.Heading>}
<p>{truncateMessage(errorDescription)}</p>
Expand All @@ -271,87 +271,93 @@ export class LibraryCreatePage extends React.Component {
]}
/>
<div className="wrapper-mast wrapper">
<header className="has-actions">
<h2 className="page-header h2">{intl.formatMessage(messages['library.form.create.library'])}</h2>
<header className="has-actions my-4">
<h2 className="page-header h2 text-primary-500">{intl.formatMessage(messages['library.form.create.library'])}</h2>
</header>
</div>
<div className="wrapper-content wrapper">
<section className="content mt-4">
<form onSubmit={this.onSubmit} className="form-create">
<fieldset>
<ol className="list-input list-unstyled">
<li className="field">
<FormGroup
name="title"
type="text"
readOnly={false}
value={data.title}
controlClassName="has-value"
handleChange={this.onValueChange}
errorMessage={this.getFieldError('title')}
floatingLabel={intl.formatMessage(messages['library.form.title.label'])}
placeholder={intl.formatMessage(messages['library.form.title.placeholder'])}
helpText={intl.formatMessage(messages['library.form.title.help'])}
/>
</li>
<li className="field">
<TypeaheadDropdown
type="text"
name="org"
readOnly={false}
value={data.org}
options={orgs}
controlClassName="has-value"
handleBlur={this.handleOnBlur}
handleChange={this.handleOnChangeOrg}
floatingLabel={intl.formatMessage(messages['library.form.org.label'])}
placeholder={intl.formatMessage(messages['library.form.org.placeholder'])}
errorMessage={this.getFieldError('org')}
helpMessage={intl.formatMessage(messages['library.form.org.help'])}
noOptionsMessage={intl.formatMessage(messages['library.organizations.list.empty'])}
/>
</li>
<li className="field">
<FormGroup
name="slug"
type="text"
readOnly={false}
value={data.slug}
controlClassName="has-value"
handleChange={this.onValueChange}
errorMessage={this.getFieldError('slug')}
floatingLabel={intl.formatMessage(messages['library.form.slug.label'])}
placeholder={intl.formatMessage(messages['library.form.slug.placeholder'])}
helpText={intl.formatMessage(messages['library.form.slug.help'])}
<Card>
<form onSubmit={this.onSubmit} className="form-create">
<Card.Section className="mt-3.5">
<fieldset>
<ol className="list-input list-unstyled">
<li className="field">
<FormGroup
name="title"
type="text"
readOnly={false}
value={data.title}
controlClassName="has-value"
handleChange={this.onValueChange}
errorMessage={this.getFieldError('title')}
floatingLabel={intl.formatMessage(messages['library.form.title.label'])}
placeholder={intl.formatMessage(messages['library.form.title.placeholder'])}
helpText={intl.formatMessage(messages['library.form.title.help'])}
/>
</li>
<li className="field my-4.5">
<TypeaheadDropdown
type="text"
name="org"
readOnly={false}
value={data.org}
options={orgs}
controlClassName="has-value"
handleBlur={this.handleOnBlur}
handleChange={this.handleOnChangeOrg}
floatingLabel={intl.formatMessage(messages['library.form.org.label'])}
placeholder={intl.formatMessage(messages['library.form.org.placeholder'])}
errorMessage={this.getFieldError('org')}
helpMessage={intl.formatMessage(messages['library.form.org.help'])}
noOptionsMessage={intl.formatMessage(messages['library.organizations.list.empty'])}
/>
</li>
<li className="field">
<FormGroup
name="slug"
type="text"
readOnly={false}
value={data.slug}
controlClassName="has-value"
handleChange={this.onValueChange}
errorMessage={this.getFieldError('slug')}
floatingLabel={intl.formatMessage(messages['library.form.slug.label'])}
placeholder={intl.formatMessage(messages['library.form.slug.placeholder'])}
helpText={intl.formatMessage(messages['library.form.slug.help'])}
/>
</li>
</ol>
</fieldset>
</Card.Section>
<Card.Footer>
<div className="actions form-group">
<Button
variant="tertiary"
onClick={this.onCancel}
className="mb-2 mb-sm-0 action btn-light"
>
{intl.formatMessage(commonMessages['library.common.forms.button.cancel'])}
</Button>
<StatefulButton
type="submit"
variant="primary"
className="action btn-primary"
state={this.getSubmitButtonState()}
disabledStates={['disabled', 'pending']}
icons={{
pending: <Icon className="fa fa-spinner fa-spin" />,
}}
labels={{
disabled: intl.formatMessage(commonMessages['library.common.forms.button.create']),
enabled: intl.formatMessage(commonMessages['library.common.forms.button.create']),
pending: intl.formatMessage(commonMessages['library.common.forms.button.creating']),
}}
/>
</li>
</ol>
</fieldset>
<div className="actions form-group">
<Button
variant="tertiary"
onClick={this.onCancel}
className="mb-2 mb-sm-0 action btn-light"
>
{intl.formatMessage(commonMessages['library.common.forms.button.cancel'])}
</Button>
<StatefulButton
type="submit"
variant="primary"
className="action btn-primary"
state={this.getSubmitButtonState()}
disabledStates={['disabled', 'pending']}
icons={{
pending: <Icon className="fa fa-spinner fa-spin" />,
}}
labels={{
disabled: intl.formatMessage(commonMessages['library.common.forms.button.create']),
enabled: intl.formatMessage(commonMessages['library.common.forms.button.create']),
pending: intl.formatMessage(commonMessages['library.common.forms.button.creating']),
}}
/>
</div>
</form>
</div>
</Card.Footer>
</form>
</Card>
</section>
</div>
</div>
Expand Down
7 changes: 6 additions & 1 deletion src/library-authoring/create-library/data/slice.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import { SUBMISSION_STATUS } from '../../common';
import { STORE_NAMES } from '../../common/data';
import messages from '../messages';

export const libraryCreateInitialState = {
createdLibrary: null,
Expand Down Expand Up @@ -32,7 +33,11 @@
},
libraryCreateFailed: (state, { payload }) => {
state.errorMessage = payload.errorMessage;
state.errorFields = payload.errorFields;
const errorFields = {};
Object.keys(payload.errorFields).forEach(field => {
errorFields[field] = messages[`library.form.field.error.empty.${field}`].defaultMessage;

Check warning on line 38 in src/library-authoring/create-library/data/slice.js

View check run for this annotation

Codecov / codecov/patch

src/library-authoring/create-library/data/slice.js#L36-L38

Added lines #L36 - L38 were not covered by tests
});
state.errorFields = errorFields;

Check warning on line 40 in src/library-authoring/create-library/data/slice.js

View check run for this annotation

Codecov / codecov/patch

src/library-authoring/create-library/data/slice.js#L40

Added line #L40 was not covered by tests
state.status = SUBMISSION_STATUS.FAILED;
},
libraryCreateClearError: (state) => {
Expand Down
17 changes: 16 additions & 1 deletion src/library-authoring/create-library/messages.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,21 @@ const messages = defineMessages({
defaultMessage: 'This field may not be blank.',
description: 'Text to display when field is empty.',
},
'library.form.field.error.empty.title': {
id: 'library.form.field.error.empty.title',
defaultMessage: 'Enter a library name',
description: 'Text to display when library name is empty.',
},
'library.form.field.error.empty.org': {
id: 'library.form.field.error.empty.org',
defaultMessage: 'Select an organization',
description: 'Text to display when organization is empty.',
},
'library.form.field.error.empty.slug': {
id: 'library.form.field.error.empty.slug',
defaultMessage: 'Enter a library ID',
description: 'Text to display when library ID is empty.',
},
'library.form.field.error.mismatch.org': {
id: 'library.form.field.error.mismatch.org',
defaultMessage: 'The organization must be selected from the options list.',
Expand Down Expand Up @@ -111,7 +126,7 @@ const messages = defineMessages({
},
'library.organizations.list.empty': {
id: 'library.organizations.list.empty',
defaultMessage: 'No options',
defaultMessage: 'No results found',
description: 'Text for empty organizations options list.',
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { libraryCreateInitialState } from '../data';
import { SUBMISSION_STATUS, ROUTES } from '../../common';
import { ctxMount } from '../../common/specs/helpers';
import { withNavigate } from '../../utils/hoc';
import messages from '../messages';

const InjectedLibraryCreatePage = injectIntl(withNavigate(LibraryCreatePage));
const config = { STUDIO_BASE_URL: 'STUDIO_BASE_URL' };
Expand Down Expand Up @@ -65,21 +66,44 @@ describe('create-library/LibraryCreatePage.jsx', () => {
expect(mockCreateLibrary).toHaveBeenCalled();
});

it('shows form errors', () => {
const newProps = { ...props, errorFields: { slug: 'Error message' } };
const container = ctxMount(
<BrowserRouter>
<InjectedLibraryCreatePage {...newProps} />
</BrowserRouter>,
{ config },
);
container.find('input').at(0).simulate('change');
container.find('input').at(1).simulate('change', { target: { value: 'org2', name: 'org' } });
container.find('input').at(1).simulate('blur');
container.find('input').at(2).simulate('change', { target: { value: '###', name: 'slug' } });
expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual('This field may not be blank.');
expect(container.find('.pgn__form-text-invalid').at(1).text()).toEqual('The organization must be selected from the options list.');
expect(container.find('.pgn__form-text-invalid').at(2).text()).toEqual('Enter a valid “slug” consisting of Unicode letters, numbers, underscores, or hyphens.');
describe('form errors', () => {
let container;
beforeEach(() => {
const newProps = { ...props, errorFields: { slug: 'Error message' } };
container = ctxMount(
<BrowserRouter>
<InjectedLibraryCreatePage {...newProps} />
</BrowserRouter>,
{ config },
);
});

it('shows empty title error', () => {
container.find('input').at(0).simulate('change');
expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.empty.title'].defaultMessage);
});

it('shows empty org error', () => {
container.find('input').at(1).simulate('change');
container.find('input').at(1).simulate('blur');
expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.empty.org'].defaultMessage);
});

it('shows empty slug error', () => {
container.find('input').at(2).simulate('change');
expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.empty.slug'].defaultMessage);
});

it('shows mismatch org error', () => {
container.find('input').at(1).simulate('change', { target: { value: 'org2', name: 'org' } });
container.find('input').at(1).simulate('blur');
expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.mismatch.org'].defaultMessage);
});

it('shows invlaid slug error', () => {
container.find('input').at(2).simulate('change', { target: { value: '###', name: 'slug' } });
expect(container.find('.pgn__form-text-invalid').at(0).text()).toEqual(messages['library.form.field.error.invalid.slug'].defaultMessage);
});
});

it('shows processing text on button', () => {
Expand Down