Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Coding Challenge - Julius #3

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
Binary file added .editorconfig
Binary file not shown.
22 changes: 20 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,20 @@
npm-debug.log
node_modules
# dependencies
/node_modules

# testing
/coverage

# production
/build

# misc
.idea/
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,26 @@ install` once starting working on the project to install dependencies.
| /books?subjects_like=Fiction | Lists all books that contain "Fiction" as a subject |
| /subjects | Lists all available subjects |

## Implementation

To save time on build process configuration [Create React App](https://github.com/facebookincubator/create-react-app) starter project was chosen as a base for this solution.

Here's the short list of libraries that has been used to speed up implementation itself.

| Library | Description |
|-----------------------------------|--------------------------------------------------------------------------|
| redux | State container for React |
| redux-form | Easy redux state connection with forms |
| react-bootstrap | Basic styling and components corresponding Bootstrap UI framework |

Redux actions and reducers were bundled together based on [ducks-modular-redux](https://github.com/erikras/ducks-modular-redux) proposal as it's more readable in single modules.

### Running the project

1. Install dependencies by running `yarn` command.
2. Start the application by running `yarn start` script.

---

More info about API usage can be found at the [json-server
repo](https://github.com/typicode/json-server).

File renamed without changes.
37 changes: 30 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,37 @@
{
"name": "react-coding-challenge",
"version": "1.0.0",
"description": "A package containing a simple dummy book API, which should be used in the challenge",
"scripts": {
"start": "json-server --port 3010 --watch api.json"
},
"keywords": ["react challenge", "tryouts", "react developer", "hiring"],
"description": "Coding challenge implementation based on create-react-app + redux + redux-form + react-bootstrap.",
"keywords": [
"react challenge",
"tryouts",
"react developer",
"hiring"
],
"author": "Adapt A/S",
"license": "ISC",
"devDependencies": {
"json-server": "0.10.1"
"scripts": {
"start": "concurrently \"yarn run start:server\" \"yarn run start:client\"",
"start:client": "react-scripts start",
"start:server": "json-server --port 3010 --watch ./api/api.json",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"dependencies": {
"bootstrap": "3",
"concurrently": "^3.5.0",
"enzyme": "^2.9.1",
"json-server": "^0.10.3",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-bootstrap": "^0.31.0",
"react-dom": "^15.6.1",
"react-redux": "^5.0.5",
"react-scripts": "1.0.10",
"react-test-renderer": "^15.6.1",
"redux": "^3.7.1",
"redux-form": "^6.8.0",
"redux-thunk": "^2.2.0"
}
}
Binary file added public/favicon.ico
Binary file not shown.
40 changes: 40 additions & 0 deletions public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<!--
manifest.json provides metadata used when your web app is added to the
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.

Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React Coding Challenge</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.

You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.

To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
15 changes: 15 additions & 0 deletions public/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"short_name": "react-coding-challenge",
"name": "react-coding-challenge",
"icons": [
{
"src": "favicon.ico",
"sizes": "192x192",
"type": "image/png"
}
],
"start_url": "./index.html",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
30 changes: 30 additions & 0 deletions src/components/App/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react';
import { Grid, Row, Col, PageHeader } from 'react-bootstrap';
import SubjectsSelector from '../../containers/SubjectsSelector/index';
import BooksList from '../../containers/BooksList/index';
import BookEditForm from '../../containers/BookEditForm/index';

const App = () => {
return (
<Grid className="App">
<Row>
<Col sm={12}>
<PageHeader>React Coding Challenge</PageHeader>
</Col>
</Row>
<Row>
<Col sm={12} lg={4}>
<SubjectsSelector />
</Col>
<Col sm={12} lg={4}>
<BooksList />
</Col>
<Col sm={12} lg={4}>
<BookEditForm />
</Col>
</Row>
</Grid>
);
};

export default App;
7 changes: 7 additions & 0 deletions src/components/App/App.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';
import { shallow } from 'enzyme';
import App from './App';

it('renders without crashing', () => {
shallow(<App />);
});
1 change: 1 addition & 0 deletions src/components/App/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './App';
26 changes: 26 additions & 0 deletions src/components/Form/FieldInput.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormControl } from 'react-bootstrap';


const FieldInput = ({ input, type, placeholder, min, max }) => {
return (
<FormControl
type={type}
placeholder={placeholder}
min={min}
max={max}
value={input.value}
onChange={input.onChange}/>
)
};

FieldInput.propTypes = {
input: PropTypes.object.isRequired,
type: PropTypes.string.isRequired,
placeholder: PropTypes.string,
min: PropTypes.string,
max: PropTypes.string,
};

export default FieldInput;
58 changes: 58 additions & 0 deletions src/components/Form/FieldInputArray.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field } from 'redux-form'
import { FormGroup, ControlLabel, Button, Row, Col } from 'react-bootstrap';
import FieldInput from './FieldInput';

const FieldInputArray = ({ fields, config }) => {
return (
<div>
{
fields.map((field, index) => (
<div key={index}>
<Row>
<Col xs={6}>
<h3>{config.title} #{index + 1}</h3>
</Col>
<Col xs={6}>
<Button
className="button--remove"
type="button"
onClick={() => fields.remove(index)}
>
Remove {config.title}
</Button>
</Col>
</Row>
{
config.children.map((child, index) => (
<FormGroup key={index}>
<ControlLabel>{child.label}</ControlLabel>
<Field
name={`${field}.${child.property}`}
component={FieldInput}
type="text"
placeholder={child.label}
/>
</FormGroup>
))
}
</div>
))
}
<Button
className="button--add" type="button"
onClick={() => fields.push({})}
>
Add {config.title}
</Button>
</div>
)
};

FieldInputArray.propTypes = {
fields: PropTypes.object.isRequired,
config: PropTypes.object.isRequired,
};

export default FieldInputArray;
57 changes: 57 additions & 0 deletions src/components/Form/FieldSet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Panel, FormGroup, ControlLabel } from 'react-bootstrap';
import { Field, FieldArray } from 'redux-form';
import FieldInput from './FieldInput';
import FieldInputArray from './FieldInputArray';

const FieldSet = ({ config }) => {
return (
<div className="FieldSet">
{
config.map((item, index) => (
item.type === 'nested' ?
<Panel key={index}>
<FieldArray
name={item.field}
component={FieldInputArray}
config={item}
/>
</Panel> : item.type === 'multiple' ?
<Panel key={index}>
<h3>Formats</h3>
{
item.children.map((name, index) => (
<FormGroup key={index}>
<ControlLabel>{name}</ControlLabel>
<Field
name={`formats["${name}"]`}
component={FieldInput}
type="text"
placeholder={item.placeholder}
/>
</FormGroup>
))
}
</Panel> :
<FormGroup key={index}>
<ControlLabel>{item.title}</ControlLabel>
<Field
name={item.field}
component={FieldInput}
type="text"
placeholder={item.title}
normalize={item.normalize}
/>
</FormGroup>
))
}
</div>
)
};

FieldSet.propTypes = {
config: PropTypes.array.isRequired,
};

export default FieldSet;
3 changes: 3 additions & 0 deletions src/components/Form/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { default as FieldSet } from './FieldSet';
export { default as FieldInput } from './FieldInput';
export { default as FieldInputArray } from './FieldInputArray';
35 changes: 35 additions & 0 deletions src/components/Selector/Selector.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from 'react';
import PropTypes from 'prop-types';
import { FormGroup, FormControl } from 'react-bootstrap';

const Selector = ({ values, onChange, options, multiple }) => (
<form>
<FormGroup>
<FormControl
componentClass="select"
multiple={multiple}
onChange={onChange}
value={multiple ? values : values[0]}
>
{
options.map((option) => (
<option value={option} key={option}>
{option}
</option>
))
}
</FormControl>
</FormGroup>
</form>
);

Selector.propTypes = {
options: PropTypes.arrayOf(
PropTypes.string.isRequired,
).isRequired,
values: PropTypes.array.isRequired,
onChange: PropTypes.func.isRequired,
multiple: PropTypes.bool,
};

export default Selector;
1 change: 1 addition & 0 deletions src/components/Selector/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Selector';
Loading