kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Developers: add challenge to become a developer
rodzic
c80d00e67f
commit
3af8313b42
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,60 +1,28 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FormattedMessage, injectIntl, defineMessages } from 'react-intl';
|
import { connect } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { getSettings } from 'soapbox/actions/settings';
|
||||||
import Column from '../ui/components/column';
|
import DevelopersMenu from './developers_menu';
|
||||||
import Icon from 'soapbox/components/icon';
|
import DevelopersChallenge from './developers_challenge';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const mapStateToProps = state => {
|
||||||
heading: { id: 'column.developers', defaultMessage: 'Developers' },
|
const settings = getSettings(state);
|
||||||
});
|
|
||||||
|
|
||||||
export default @injectIntl
|
return {
|
||||||
|
isDeveloper: settings.get('isDeveloper'),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default @connect(mapStateToProps)
|
||||||
class Developers extends React.Component {
|
class Developers extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
intl: PropTypes.object.isRequired,
|
isDeveloper: PropTypes.bool.isRequired,
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { intl } = this.props;
|
const { isDeveloper } = this.props;
|
||||||
|
return isDeveloper ? <DevelopersMenu /> : <DevelopersChallenge />;
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.' />}
|
hint={<FormattedMessage id='preferences.hints.demetricator' defaultMessage='Decrease social media anxiety by hiding all numbers from the site.' />}
|
||||||
path={['demetricator']}
|
path={['demetricator']}
|
||||||
/>
|
/>
|
||||||
<SettingsCheckbox
|
|
||||||
label={<FormattedMessage id='preferences.fields.developer_label' defaultMessage='Developer tools' />}
|
|
||||||
path={['isDeveloper']}
|
|
||||||
/>
|
|
||||||
</FieldsGroup>
|
</FieldsGroup>
|
||||||
</SimpleForm>
|
</SimpleForm>
|
||||||
</Column>
|
</Column>
|
||||||
|
|
|
@ -324,7 +324,7 @@ class SwitchingColumnsArea extends React.PureComponent {
|
||||||
|
|
||||||
<WrappedRoute path='/developers/apps/create' developerOnly page={DefaultPage} component={CreateApp} content={children} />
|
<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/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='/error' page={EmptyPage} component={IntentionalError} content={children} />
|
||||||
|
|
||||||
<WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />
|
<WrappedRoute path='/donate/crypto' publicRoute page={DefaultPage} component={CryptoDonate} content={children} />
|
||||||
|
|
|
@ -31,6 +31,7 @@
|
||||||
@import 'navigation';
|
@import 'navigation';
|
||||||
@import 'placeholder';
|
@import 'placeholder';
|
||||||
@import 'autosuggest';
|
@import 'autosuggest';
|
||||||
|
@import 'developers';
|
||||||
|
|
||||||
// COMPONENTS
|
// COMPONENTS
|
||||||
@import 'components/buttons';
|
@import 'components/buttons';
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
Ładowanie…
Reference in New Issue