Skip to content

Commit 672ec40

Browse files
committed
Initial commit
0 parents  commit 672ec40

25 files changed

+28377
-0
lines changed

.eslintrc.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"env": {
3+
"commonjs": true,
4+
"es2021": true,
5+
"node": true,
6+
"browser": true,
7+
"jest": true
8+
},
9+
"extends": [
10+
"eslint:recommended",
11+
"plugin:react/recommended"
12+
],
13+
"parserOptions": {
14+
"ecmaVersion": "latest",
15+
"sourceType": "module"
16+
},
17+
"rules": {}
18+
}

.gitignore

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
# Logs
2+
logs
3+
*.log
4+
npm-debug.log*
5+
yarn-debug.log*
6+
yarn-error.log*
7+
lerna-debug.log*
8+
.pnpm-debug.log*
9+
10+
# Diagnostic reports (https://nodejs.org/api/report.html)
11+
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12+
13+
# Runtime data
14+
pids
15+
*.pid
16+
*.seed
17+
*.pid.lock
18+
19+
# Directory for instrumented libs generated by jscoverage/JSCover
20+
lib-cov
21+
22+
# Coverage directory used by tools like istanbul
23+
coverage
24+
*.lcov
25+
26+
# nyc test coverage
27+
.nyc_output
28+
29+
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30+
.grunt
31+
32+
# Bower dependency directory (https://bower.io/)
33+
bower_components
34+
35+
# node-waf configuration
36+
.lock-wscript
37+
38+
# Compiled binary addons (https://nodejs.org/api/addons.html)
39+
build/Release
40+
41+
# Dependency directories
42+
node_modules/
43+
jspm_packages/
44+
45+
# Snowpack dependency directory (https://snowpack.dev/)
46+
web_modules/
47+
48+
# TypeScript cache
49+
*.tsbuildinfo
50+
51+
# Optional npm cache directory
52+
.npm
53+
54+
# Optional eslint cache
55+
.eslintcache
56+
57+
# Optional stylelint cache
58+
.stylelintcache
59+
60+
# Microbundle cache
61+
.rpt2_cache/
62+
.rts2_cache_cjs/
63+
.rts2_cache_es/
64+
.rts2_cache_umd/
65+
66+
# Optional REPL history
67+
.node_repl_history
68+
69+
# Output of 'npm pack'
70+
*.tgz
71+
72+
# Yarn Integrity file
73+
.yarn-integrity
74+
75+
# dotenv environment variable files
76+
.env
77+
.env.development.local
78+
.env.test.local
79+
.env.production.local
80+
.env.local
81+
82+
# parcel-bundler cache (https://parceljs.org/)
83+
.cache
84+
.parcel-cache
85+
86+
# Next.js build output
87+
.next
88+
out
89+
90+
# Nuxt.js build / generate output
91+
.nuxt
92+
93+
# Gatsby files
94+
.cache/
95+
# Comment in the public line in if your project uses Gatsby and not Next.js
96+
# https://nextjs.org/blog/next-9-1#public-directory-support
97+
# public
98+
99+
# vuepress build output
100+
.vuepress/dist
101+
dist
102+
103+
# vuepress v2.x temp and cache directory
104+
.temp
105+
.cache
106+
107+
# Docusaurus cache and generated files
108+
.docusaurus
109+
110+
# Serverless directories
111+
.serverless/
112+
113+
# FuseBox cache
114+
.fusebox/
115+
116+
# DynamoDB Local files
117+
.dynamodb/
118+
119+
# TernJS port file
120+
.tern-port
121+
122+
# Stores VSCode versions used for testing VSCode extensions
123+
.vscode-test
124+
125+
# yarn v2
126+
.yarn/cache
127+
.yarn/unplugged
128+
.yarn/build-state.yml
129+
.yarn/install-state.gz
130+
.pnp.*
131+
132+
.vscode
133+
.DS_Store

README.md

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Sprint Challenge: Advanced React
2+
3+
## Intro
4+
5+
In this challenge, you will write the logic for [THIS](https://the-gabe-grid.herokuapp.com/) widget.
6+
7+
Study its functionality and also inspect the Console, the Network tab and the Elements tab in Chrome Dev Tools:
8+
9+
- There are two versions of the widget, with identical functionality.
10+
- The input box at the bottom of the page expects a valid email address.
11+
- Email validation errors arrive from the server, as you can see in the Network tab, under "Preview".
12+
- The payload sent to the server on form submit can also be seen in the Network tab, under "Payload".
13+
- The origin of coordinates of the grid is at its top-left corner.
14+
- One valid email in particular, `[email protected]`, **results in a server error.**
15+
16+
## Requirements
17+
18+
### Tools
19+
20+
- Node 16.x
21+
- NPM 8.x (update NPM executing `npm i -g npm`)
22+
- Chrome >= 96.x
23+
24+
Other configurations might work but haven't been tested.
25+
26+
### Optional Tools
27+
28+
- Postman (download [here](https://www.postman.com/downloads/))
29+
30+
## Project Setup
31+
32+
- Fork, clone, and `npm install`. You don't need to add any extra libraries.
33+
- Launch the project on a dev server running `npm run dev`.
34+
- See your widget visiting `http://localhost:3000` in Chrome.
35+
- Run tests locally executing `npm test`. The test file is `codegrade_mvp.test`.
36+
37+
## API
38+
39+
- The application includes an endpoint on `POST http://localhost:9000/result`.
40+
- You can experiment with this endpoint using an HTTP client like Postman.
41+
- The endpoint expects a payload like `{ "x": 1, "y": 2, "steps": 3, "email": "[email protected]" }`:
42+
- `x` is an integer between 1 and 3.
43+
- `y` is an integer between 1 and 3.
44+
- `steps` is an integer larger than 0.
45+
- `email` is a valid email address.
46+
47+
## MVP
48+
49+
### MVP 1, The Grid -- Long Explanation
50+
51+
- Replicate the **functionality and DOM** shown in the prototype linked at the top of this README.
52+
- Keep your code inside `frontend/components/AppFunctional.js` and `frontend/components/AppClass.js`.
53+
- The component exposed by `AppFunctional.js` must be a stateful functional component.
54+
- The one in `AppClass.js` must be a stateful class-based component.
55+
- The DOM produced by your components must match exactly the DOM in the prototype:
56+
- The HTML hierarchy, ids, class names etc must be the same.
57+
- The current square is marked with a capital B.
58+
- Success and error messages must be those sent by the API (see Network tab -> "Preview").
59+
- No frontend form validation code is required.
60+
- The coordinates of each square of the grid are as follows:
61+
62+
```
63+
(1, 1) (2, 1) (3, 1)
64+
(1, 2) (2, 2) (3, 2)
65+
(1, 3) (2, 3) (3, 3)
66+
```
67+
68+
### MVP 1, The Grid -- Short Explanation
69+
70+
- Make **ALL** the tests pass!
71+
72+
### MVP 2, Testing
73+
74+
- Using `codegrade_mvp.test.js` as inspiration, write a few tests inside `frontend/components/App.test.js`:
75+
- From inside the test file, import a component of your choosing, either `AppClass.js` or `AppFunctional.js`.
76+
- Test that emails over 100 characters long result in a particular server error on the screen.
77+
- Inspect the API with Postman to see what this error actually is.
78+
79+
### Stretch Goals
80+
81+
- Extract some of the stateful logic into a custom hook at the top of `AppFunctional.js`.
82+
- Build a stateless component responsible for rendering the markup in the stateful components, so it does not have to be repeated.
83+
- Do not break your MVP by pushing broken stretch goals. You must keep your tests passing at 100%.
84+
85+
### Important Notes
86+
87+
- Design the state of the app before opening your editor. You might need fewer pieces of state than you think!
88+
- Find the simplest data structure that describes effectively the state of the grid.
89+
- "Product" works hard designing the messages: we must reproduce them faithfully, down to the last comma.
90+
- If you start with Functional, don't switch to Class-Based until Functional is passing all tests (and viceversa).
91+
- If the direction of the `y` axis surprises you, know that elements in HTML also have their origin of coordinates at their top-left corner.

babel.config.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const PLUGIN_TRANSFORM_RUNTIME = '@babel/plugin-transform-runtime'
2+
const PLUGIN_STYLED_COMPONENTS = 'babel-plugin-styled-components'
3+
4+
const PRESET_REACT = '@babel/preset-react'
5+
const PRESET_ENV = '@babel/preset-env'
6+
7+
module.exports = {
8+
env: {
9+
testing: {
10+
plugins: [
11+
[PLUGIN_TRANSFORM_RUNTIME],
12+
],
13+
presets: [
14+
[PRESET_REACT],
15+
[PRESET_ENV, { modules: 'commonjs', debug: false }]
16+
]
17+
},
18+
development: {
19+
plugins: [
20+
[PLUGIN_STYLED_COMPONENTS],
21+
],
22+
presets: [
23+
[PRESET_REACT],
24+
[PRESET_ENV, { targets: { chrome: '96' } }]
25+
]
26+
},
27+
production: {
28+
plugins: [
29+
[PLUGIN_STYLED_COMPONENTS],
30+
],
31+
presets: [
32+
[PRESET_REACT],
33+
[PRESET_ENV, { targets: { chrome: '96' } }]
34+
]
35+
},
36+
}
37+
}

backend/helpers.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
const yup = require('yup')
2+
3+
const schema = yup.object().shape({
4+
email: yup
5+
.string()
6+
.trim()
7+
.email('email must be a valid email')
8+
.required('email is required')
9+
.max(100, 'email must be under 100 chars'),
10+
x: yup
11+
.number()
12+
.typeError('x coordinate must be a number')
13+
.required('x coordinate is required')
14+
.max(3, 'x coordinate must be 1, 2 or 3')
15+
.min(1, 'x coordinate must be 1, 2 or 3'),
16+
y: yup
17+
.number()
18+
.typeError('y coordinate must be a number')
19+
.required('y coordinate is required')
20+
.max(3, 'y coordinate must be 1, 2 or 3')
21+
.min(1, 'y coordinate must be 1, 2 or 3'),
22+
steps: yup
23+
.number()
24+
.typeError('steps must be a number')
25+
.required('steps is required')
26+
.min(0, 'steps must be 0 or greater'),
27+
})
28+
29+
async function buildResponse(req) {
30+
let status = 200
31+
let message
32+
33+
try {
34+
const validated = await schema.validate(req.body, { stripUnknown: true })
35+
36+
const { email, x, y, steps } = validated
37+
const code = (((x + 1) * (y + 2)) * (steps + 1)) + email.length
38+
39+
if (email === '[email protected]') {
40+
message = `[email protected] failure #${code}`
41+
status = 403
42+
} else {
43+
const name = email.split('@')[0]
44+
message = `${name} win #${code}`
45+
}
46+
} catch (err) {
47+
message = `Ouch: ${err.message}`
48+
status = 422
49+
}
50+
51+
return [status, { message }]
52+
}
53+
54+
module.exports = {
55+
buildResponse,
56+
}

backend/mock-server.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const { setupServer } = require('msw/node')
2+
const { rest } = require('msw')
3+
4+
const { buildResponse } = require('./helpers')
5+
6+
async function result(req, res, ctx) {
7+
const [status, payload] = await buildResponse(req)
8+
return res(
9+
ctx.status(status),
10+
ctx.json(payload),
11+
)
12+
}
13+
14+
function catchAll(req, res, ctx) {
15+
const message = `Endpoint [${req.method}] /${req.params['0']} does not exist`
16+
return res(
17+
ctx.status(404),
18+
ctx.json({ message }),
19+
)
20+
}
21+
22+
const handlers = [
23+
rest.post('http://localhost:9000/api/result', result),
24+
rest.all('http://localhost:9000/*', catchAll),
25+
]
26+
27+
module.exports = setupServer(...handlers)

backend/server.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
const express = require('express')
2+
const cors = require('cors')
3+
const path = require('path')
4+
5+
const { buildResponse } = require('./helpers')
6+
7+
const server = express()
8+
9+
server.use(express.json())
10+
11+
server.use(express.static(path.join(__dirname, '../dist')))
12+
13+
server.use(cors())
14+
15+
server.post('/api/result', async (req, res) => {
16+
const [status, payload] = await buildResponse(req)
17+
res.status(status).json(payload)
18+
})
19+
20+
server.get('*', (req, res) => {
21+
res.sendFile(path.join(__dirname, '../dist/index.html'))
22+
})
23+
24+
server.use((req, res) => {
25+
res.status(404).json({
26+
message: `Endpoint [${req.method}] ${req.path} does not exist`,
27+
})
28+
})
29+
30+
module.exports = server

0 commit comments

Comments
 (0)