Developers: add challenge to become a developer

This commit is contained in:
Alex Gleason 2022-01-07 19:27:18 -06:00
parent c80d00e67f
commit 3af8313b42
No known key found for this signature in database
GPG key ID: 7211D1F99744FBB7
7 changed files with 182 additions and 52 deletions

View file

@ -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>
);
}
}

View file

@ -0,0 +1,60 @@
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';
const messages = defineMessages({
heading: { id: 'column.developers', defaultMessage: 'Developers' },
});
export default @injectIntl
class DevelopersMenu extends React.Component {
static propTypes = {
intl: PropTypes.object.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>
);
}
}

View file

@ -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 />;
}
}

View file

@ -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>

View file

@ -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} />

View file

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

View file

@ -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;
}
}