Merge branch 'developers-challenge' into 'develop'

Developers: add challenge to become a developer

See merge request soapbox-pub/soapbox-fe!973
party-hat
Alex Gleason 2022-01-08 02:05:31 +00:00
commit af556d75b6
7 zmienionych plików z 212 dodań i 52 usunięć

Wyświetl plik

@ -0,0 +1,84 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import Column from '../ui/components/column';
import { SimpleForm, TextInput } from 'soapbox/features/forms';
import { changeSetting } from 'soapbox/actions/settings';
import snackbar from 'soapbox/actions/snackbar';
const messages = defineMessages({
heading: { id: 'column.developers', defaultMessage: 'Developers' },
answerLabel: { id: 'developers.challenge.answer_label', defaultMessage: 'Answer' },
answerPlaceholder: { id: 'developers.challenge.answer_placeholder', defaultMessage: 'Your answer' },
success: { id: 'developers.challenge.success', defaultMessage: 'You are now a developer' },
fail: { id: 'developers.challenge.fail', defaultMessage: 'Wrong answer' },
});
export default @connect()
@injectIntl
class DevelopersChallenge extends React.Component {
static propTypes = {
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
}
state = {
answer: '',
}
handleChangeAnswer = e => {
this.setState({ answer: e.target.value });
}
handleSubmit = e => {
const { intl, dispatch } = this.props;
const { answer } = this.state;
if (answer === 'buzzfizz') {
dispatch(changeSetting(['isDeveloper'], true));
dispatch(snackbar.success(intl.formatMessage(messages.success)));
} else {
dispatch(snackbar.error(intl.formatMessage(messages.fail)));
}
}
render() {
const { intl } = this.props;
const challenge = `function fizzbuzz() {
return 'fizz|buzz'.split('|').reverse().join('');
}`;
return (
<Column heading={intl.formatMessage(messages.heading)}>
<div className='developers-challenge'>
<SimpleForm onSubmit={this.handleSubmit}>
<FormattedMessage
id='developers.challenge.message'
defaultMessage='What is the result of calling {function}?'
values={{ function: <span className='code'>fizzbuzz()</span> }}
/>
<pre className='code'>
{challenge}
</pre>
<TextInput
name='answer'
label={intl.formatMessage(messages.answerLabel)}
placeholder={intl.formatMessage(messages.answerPlaceholder)}
onChange={this.handleChangeAnswer}
value={this.state.answer}
/>
<div className='actions'>
<button name='button' type='submit' className='btn button button-primary'>
<FormattedMessage id='developers.challenge.submit' defaultMessage='Become a developer' />
</button>
</div>
</SimpleForm>
</div>
</Column>
);
}
}

Wyświetl plik

@ -0,0 +1,90 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { Link } from 'react-router-dom';
import Column from '../ui/components/column';
import Icon from 'soapbox/components/icon';
import { changeSetting } from 'soapbox/actions/settings';
import snackbar from 'soapbox/actions/snackbar';
const messages = defineMessages({
heading: { id: 'column.developers', defaultMessage: 'Developers' },
leave: { id: 'developers.leave', defaultMessage: 'You have left developers' },
});
export default @connect()
@injectIntl
class DevelopersMenu extends React.Component {
static contextTypes = {
router: PropTypes.object,
};
static propTypes = {
intl: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired,
}
leaveDevelopers = e => {
const { intl, dispatch } = this.props;
dispatch(changeSetting(['isDeveloper'], false));
dispatch(snackbar.success(intl.formatMessage(messages.leave)));
this.context.router.history.push('/');
e.preventDefault();
}
render() {
const { intl } = this.props;
return (
<Column heading={intl.formatMessage(messages.heading)}>
<div className='dashcounters'>
<div className='dashcounter'>
<Link to='/developers/apps/create'>
<div className='dashcounter__icon'>
<Icon src={require('@tabler/icons/icons/apps.svg')} />
</div>
<div className='dashcounter__label'>
<FormattedMessage id='developers.navigation.app_create_label' defaultMessage='Create an app' />
</div>
</Link>
</div>
<div className='dashcounter'>
<Link to='/developers/settings_store'>
<div className='dashcounter__icon'>
<Icon src={require('@tabler/icons/icons/code-plus.svg')} />
</div>
<div className='dashcounter__label'>
<FormattedMessage id='developers.navigation.settings_store_label' defaultMessage='Settings store' />
</div>
</Link>
</div>
<div className='dashcounter'>
<Link to='/error'>
<div className='dashcounter__icon'>
<Icon src={require('@tabler/icons/icons/mood-sad.svg')} />
</div>
<div className='dashcounter__label'>
<FormattedMessage id='developers.navigation.intentional_error_label' defaultMessage='Trigger an error' />
</div>
</Link>
</div>
<div className='dashcounter'>
<a href='#' onClick={this.leaveDevelopers}>
<div className='dashcounter__icon'>
<Icon src={require('@tabler/icons/icons/logout.svg')} />
</div>
<div className='dashcounter__label'>
<FormattedMessage id='developers.navigation.leave_developers_label' defaultMessage='Leave developers' />
</div>
</a>
</div>
</div>
</Column>
);
}
}

Wyświetl plik

@ -1,60 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
import { Link } from 'react-router-dom';
import Column from '../ui/components/column';
import Icon from 'soapbox/components/icon';
import { connect } from 'react-redux';
import { getSettings } from 'soapbox/actions/settings';
import DevelopersMenu from './developers_menu';
import DevelopersChallenge from './developers_challenge';
const messages = defineMessages({
heading: { id: 'column.developers', defaultMessage: 'Developers' },
});
const mapStateToProps = state => {
const settings = getSettings(state);
export default @injectIntl
return {
isDeveloper: settings.get('isDeveloper'),
};
};
export default @connect(mapStateToProps)
class Developers extends React.Component {
static propTypes = {
intl: PropTypes.object.isRequired,
isDeveloper: PropTypes.bool.isRequired,
}
render() {
const { intl } = this.props;
return (
<Column heading={intl.formatMessage(messages.heading)}>
<div className='dashcounters'>
<div className='dashcounter'>
<Link to='/developers/apps/create'>
<div className='dashcounter__icon'>
<Icon src={require('@tabler/icons/icons/apps.svg')} />
</div>
<div className='dashcounter__label'>
<FormattedMessage id='developers.navigation.app_create_label' defaultMessage='Create an app' />
</div>
</Link>
</div>
<div className='dashcounter'>
<Link to='/developers/settings_store'>
<div className='dashcounter__icon'>
<Icon src={require('@tabler/icons/icons/code-plus.svg')} />
</div>
<div className='dashcounter__label'>
<FormattedMessage id='developers.navigation.settings_store_label' defaultMessage='Settings store' />
</div>
</Link>
</div>
<div className='dashcounter'>
<Link to='/error'>
<div className='dashcounter__icon'>
<Icon src={require('@tabler/icons/icons/mood-sad.svg')} />
</div>
<div className='dashcounter__label'>
<FormattedMessage id='developers.navigation.intentional_error_label' defaultMessage='Trigger an error' />
</div>
</Link>
</div>
</div>
</Column>
);
const { isDeveloper } = this.props;
return isDeveloper ? <DevelopersMenu /> : <DevelopersChallenge />;
}
}

Wyświetl plik

@ -282,10 +282,6 @@ class Preferences extends ImmutablePureComponent {
hint={<FormattedMessage id='preferences.hints.demetricator' defaultMessage='Decrease social media anxiety by hiding all numbers from the site.' />}
path={['demetricator']}
/>
<SettingsCheckbox
label={<FormattedMessage id='preferences.fields.developer_label' defaultMessage='Developer tools' />}
path={['isDeveloper']}
/>
</FieldsGroup>
</SimpleForm>
</Column>

Wyświetl plik

@ -324,7 +324,7 @@ class SwitchingColumnsArea extends React.PureComponent {
<WrappedRoute path='/developers/apps/create' developerOnly page={DefaultPage} component={CreateApp} content={children} />
<WrappedRoute path='/developers/settings_store' developerOnly page={DefaultPage} component={SettingsStore} content={children} />
<WrappedRoute path='/developers' developerOnly page={DefaultPage} component={Developers} content={children} />
<WrappedRoute path='/developers' page={DefaultPage} component={Developers} content={children} />
<WrappedRoute path='/error' page={EmptyPage} component={IntentionalError} content={children} />
<WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />

Wyświetl plik

@ -31,6 +31,7 @@
@import 'navigation';
@import 'placeholder';
@import 'autosuggest';
@import 'developers';
// COMPONENTS
@import 'components/buttons';

Wyświetl plik

@ -0,0 +1,21 @@
.developers-challenge {
.code {
font-family: 'Roboto Mono', monospace;
cursor: text;
background-color: var(--background-color);
}
span.code {
padding: 2px 4px;
border-radius: 4px;
}
pre.code {
line-height: 1.6em;
overflow-x: auto;
border-radius: 6px;
padding: 8px 12px;
margin: 20px 0;
word-break: break-all;
}
}