├── .babelrc
├── .gitignore
├── Map-with users.png
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── index.js
├── logo192.png
└── logo512.png
├── src
├── assets
│ └── conference-map.svg
└── components
│ ├── App
│ ├── App.css
│ └── index.tsx
│ └── Theater
│ ├── Theater.css
│ ├── index.tsx
│ └── tableConfig.json
└── tsconfig.json
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env",
4 | "@babel/preset-react",
5 | "@babel/preset-typescript"
6 | ],
7 | "plugins": [
8 | "@babel/plugin-transform-runtime",
9 | "effector/babel-plugin",
10 | "effector/babel-plugin-react"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | config.json
4 | .cache
--------------------------------------------------------------------------------
/Map-with users.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YanLobat/effector-workshop/969127ba93a2ae2375a4975e15f8c6f107f833f6/Map-with users.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Effector workshop
2 |
3 | This is an educational app based on effector. We'll go step by step building it from starter boilerplate.
4 | It's about small MVP for conference rooms.
5 |
6 | [RU] [Workshop video](https://youtu.be/sn9yszY7gn0?t=1568)
7 |
8 | ## Functionality
9 |
10 | - Google login/email login to authenticate user
11 | - Simple routing. Two routes `/` for login and `/theater` which one as authenticated i.e. user can go into this page only if user is logged in
12 | - Once user is authenticated, users will be redirected into this theater page
13 | - Users can go into any table and their avatars will be shown as below.
14 | 
15 | - Assign a table to the user when they land on this page
16 | - Assignment logic: First user will go into first table, second user will be paired with first user in first table. Third user will go into second table and fourth will be paired with third user in second table. Once all the tables have 2 people, next incoming user will go into first table and then second and so on.
17 | - Users can switch table at any point of time
18 | - If user refreshes the browser at any point of time, they should land on same table
19 | - At any point of time, one user can be in only one room
20 | - When a table is full and new user tries to enter, show error notification
21 | - When all tables are full show error notification after login attempt
22 |
23 | ## Bonus
24 | - Could test max capacity in chapter6
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "effector-workshop",
3 | "version": "0.1.0",
4 | "description": "Educational app based on effector",
5 | "main": "index.js",
6 | "scripts": {
7 | "start": "parcel public/index.html --no-cache",
8 | "build": "parcel build -d dist --no-autoinstall --public-url /try public/index.html"
9 | },
10 | "keywords": [
11 | "effector"
12 | ],
13 | "author": "YanLobat",
14 | "license": "MIT",
15 | "dependencies": {
16 | "@types/react": "^16.9.46",
17 | "effector": "^21.2.0",
18 | "effector-react": "^21.0.4",
19 | "firebase": "^7.18.0",
20 | "react": "^16.9.0",
21 | "react-dom": "^16.9.0"
22 | },
23 | "devDependencies": {
24 | "@babel/core": "^7.11.0",
25 | "@babel/plugin-transform-runtime": "^7.10.5",
26 | "@babel/plugin-transform-strict-mode": "^7.2.0",
27 | "@babel/preset-env": "^7.4.3",
28 | "@babel/preset-react": "^7.0.0",
29 | "@babel/preset-typescript": "^7.10.4",
30 | "@babel/standalone": "^7.4.3",
31 | "@babel/register": "^7.10.5",
32 | "parcel-bundler": "^1.12.3",
33 | "typescript": "^3.9.7"
34 | },
35 | "browserslist": [
36 | "last 2 Chrome versions",
37 | "last 2 Firefox versions",
38 | "last 2 Safari versions"
39 | ]
40 | }
41 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YanLobat/effector-workshop/969127ba93a2ae2375a4975e15f8c6f107f833f6/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 | Remo Coding Challenge Join Room
14 |
15 |
16 | You need to enable JavaScript to run this app.
17 |
18 |
28 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/public/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 |
4 | import {App} from '../src/components/App'
5 |
6 | ReactDOM.render(
7 | ,
8 | document.getElementById('root')
9 | )
10 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YanLobat/effector-workshop/969127ba93a2ae2375a4975e15f8c6f107f833f6/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/YanLobat/effector-workshop/969127ba93a2ae2375a4975e15f8c6f107f833f6/public/logo512.png
--------------------------------------------------------------------------------
/src/components/App/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/components/App/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 | import './App.css'
3 | import {Theater} from '../Theater'
4 |
5 | export const App = () => {
6 | return (
7 |
8 | )
9 | }
--------------------------------------------------------------------------------
/src/components/Theater/Theater.css:
--------------------------------------------------------------------------------
1 | .remo-theater{
2 | margin-left: -378px;
3 | margin-top: -300px;
4 | }
5 | .rt-app-bar {
6 | padding: 1rem;
7 | position: fixed;
8 | display: flex;
9 | justify-content: flex-end;
10 | align-items: center;
11 | margin-left: 378px;
12 | margin-top: 300px;
13 | z-index: 1;
14 | width: 100%;
15 | box-sizing: border-box;
16 | }
17 |
18 | .rt-rooms {
19 | position: absolute;
20 | z-index: 1;
21 | }
22 | .rt-room {
23 | position: absolute;
24 | cursor: pointer;
25 | }
26 | .rt-room-name {
27 | font-size: 0.875rem;
28 | background: #fff;
29 | position: absolute;
30 | bottom: 0;
31 | text-align: center;
32 | width: 100%;
33 | }
34 |
35 | .rt-room:hover {
36 | background: rgba(0, 0, 0, 0.1);
37 | border-radius: 4px;
38 | }
39 | .rt-background {
40 | position: absolute;
41 | }
42 | .rt-background img {
43 | object-fit: cover;
44 | }
45 | .email {
46 | margin-left: 8px;
47 | margin-right: 15px;
48 | }
49 | .rt-seat {
50 | position: absolute;
51 | width: 51px;
52 | height: 51px;
53 | border-radius: 50%;
54 | border: 2px solid green;
55 | overflow: hidden;
56 | }
57 | .seat-person {
58 | display: block;
59 | width: 100%;
60 | height: 100%;
61 | }
--------------------------------------------------------------------------------
/src/components/Theater/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react'
2 |
3 | import './Theater.css'
4 | // @ts-ignore
5 | import MapImage from '../../assets/conference-map.svg'
6 | import {tables, width, height} from './tableConfig.json'
7 |
8 | export const Theater: React.FC = () => {
9 | const firstTable = tables[0]
10 |
11 | return (
12 |
13 |
14 | {/**
15 | * Show user profile pic/name after login
16 | */}
17 |
Logout
18 |
19 |
20 | {/**
21 | * Create rooms here as in the requirement and make sure it is aligned with background
22 | */}
23 |
24 |
25 |
26 |
27 |
28 |
29 | )
30 | }
31 |
--------------------------------------------------------------------------------
/src/components/Theater/tableConfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "width": 2691,
3 | "height": 1766,
4 | "tables": [
5 | {
6 | "id": "first-table",
7 | "x": 712.706,
8 | "y": 588.577,
9 | "width": 240.153,
10 | "height": 248.875,
11 | "seats": [
12 | {
13 | "x": 91.92,
14 | "y": 5.68
15 | },
16 | {
17 | "x": 91.92,
18 | "y": 147.23
19 | },
20 | {
21 | "x": 162.52,
22 | "y": 76.23
23 | },
24 | {
25 | "x": 22.15,
26 | "y": 76.23
27 | },
28 | {
29 | "x": 22.15,
30 | "y": 5.68
31 | },
32 | {
33 | "x": 162.52,
34 | "y": 147.23
35 | }
36 | ]
37 | },
38 | {
39 | "id": "second-table",
40 | "x": 969.032,
41 | "y": 588.577,
42 | "width": 240.153,
43 | "height": 248.875,
44 | "seats": [
45 | {
46 | "x": 89.58,
47 | "y": 12.030000000000001
48 | },
49 | {
50 | "x": 89.58,
51 | "y": 141.26
52 | },
53 | {
54 | "x": 167.69,
55 | "y": 77.01
56 | },
57 | {
58 | "x": 16.29,
59 | "y": 77.01
60 | },
61 | {
62 | "x": 16.29,
63 | "y": 12.030000000000001
64 | },
65 | {
66 | "x": 167.69,
67 | "y": 141.26
68 | }
69 | ]
70 | },
71 | {
72 | "id": "third-table",
73 | "x": 1226.03,
74 | "y": 588.577,
75 | "width": 240.153,
76 | "height": 248.875,
77 | "seats": [
78 | {
79 | "x": 91.99,
80 | "y": 5.329999999999998
81 | },
82 | {
83 | "x": 91.99,
84 | "y": 146.88
85 | },
86 | {
87 | "x": 156.47,
88 | "y": 75.88
89 | },
90 | {
91 | "x": 22.22,
92 | "y": 75.88
93 | },
94 | {
95 | "x": 22.22,
96 | "y": 5.329999999999998
97 | },
98 | {
99 | "x": 156.47,
100 | "y": 146.88
101 | }
102 | ]
103 | },
104 | {
105 | "id": "fourth-table",
106 | "x": 1483.03,
107 | "y": 588.577,
108 | "width": 240.153,
109 | "height": 248.875,
110 | "seats": [
111 | {
112 | "x": 89.97,
113 | "y": 11.68
114 | },
115 | {
116 | "x": 89.97,
117 | "y": 140.91
118 | },
119 | {
120 | "x": 168.08,
121 | "y": 76.66
122 | },
123 | {
124 | "x": 16.67,
125 | "y": 76.66
126 | },
127 | {
128 | "x": 16.67,
129 | "y": 11.68
130 | },
131 | {
132 | "x": 168.08,
133 | "y": 140.91
134 | }
135 | ]
136 | },
137 | {
138 | "id": "fifth-table",
139 | "x": 1736.89,
140 | "y": 588.577,
141 | "width": 240.153,
142 | "height": 248.875,
143 | "seats": [
144 | {
145 | "x": 91.93,
146 | "y": 5.590000000000003
147 | },
148 | {
149 | "x": 91.93,
150 | "y": 147.14
151 | },
152 | {
153 | "x": 162.53,
154 | "y": 76.14
155 | },
156 | {
157 | "x": 22.159999999999997,
158 | "y": 76.14
159 | },
160 | {
161 | "x": 22.159999999999997,
162 | "y": 5.590000000000003
163 | },
164 | {
165 | "x": 162.53,
166 | "y": 147.14
167 | }
168 | ]
169 | },
170 | {
171 | "id": "sixth-table",
172 | "x": 712.49,
173 | "y": 849.943,
174 | "width": 240.153,
175 | "height": 248.875,
176 | "seats": [
177 | {
178 | "x": 92.14,
179 | "y": 5.32
180 | },
181 | {
182 | "x": 92.14,
183 | "y": 146.87
184 | },
185 | {
186 | "x": 162.74,
187 | "y": 75.87
188 | },
189 | {
190 | "x": 22.369999999999997,
191 | "y": 75.87
192 | },
193 | {
194 | "x": 22.369999999999997,
195 | "y": 5.32
196 | },
197 | {
198 | "x": 162.74,
199 | "y": 146.87
200 | }
201 | ]
202 | },
203 | {
204 | "id": "seventh-table",
205 | "x": 968.816,
206 | "y": 849.943,
207 | "width": 240.153,
208 | "height": 248.875,
209 | "seats": [
210 | {
211 | "x": 89.76,
212 | "y": 12.009999999999998
213 | },
214 | {
215 | "x": 89.76,
216 | "y": 141.24
217 | },
218 | {
219 | "x": 167.88,
220 | "y": 76.99
221 | },
222 | {
223 | "x": 16.47,
224 | "y": 76.99
225 | },
226 | {
227 | "x": 16.47,
228 | "y": 12.009999999999998
229 | },
230 | {
231 | "x": 167.88,
232 | "y": 141.24
233 | }
234 | ]
235 | },
236 | {
237 | "id": "eighth-table",
238 | "x": 1225.82,
239 | "y": 849.943,
240 | "width": 240.153,
241 | "height": 248.875,
242 | "seats": [
243 | {
244 | "x": 92.2,
245 | "y": 4.960000000000001
246 | },
247 | {
248 | "x": 92.2,
249 | "y": 138.56
250 | },
251 | {
252 | "x": 162.8,
253 | "y": 75.51
254 | },
255 | {
256 | "x": 22.43,
257 | "y": 75.51
258 | },
259 | {
260 | "x": 22.43,
261 | "y": 4.960000000000001
262 | },
263 | {
264 | "x": 162.8,
265 | "y": 138.56
266 | }
267 | ]
268 | },
269 | {
270 | "id": "ninth-table",
271 | "x": 1482.82,
272 | "y": 849.943,
273 | "width": 240.153,
274 | "height": 248.875,
275 | "seats": [
276 | {
277 | "x": 90.15,
278 | "y": 11.649999999999999
279 | },
280 | {
281 | "x": 90.15,
282 | "y": 140.88
283 | },
284 | {
285 | "x": 168.27,
286 | "y": 76.63
287 | },
288 | {
289 | "x": 16.86,
290 | "y": 72.56
291 | },
292 | {
293 | "x": 16.86,
294 | "y": 11.649999999999999
295 | },
296 | {
297 | "x": 168.27,
298 | "y": 140.88
299 | }
300 | ]
301 | },
302 | {
303 | "id": "tenth-table",
304 | "x": 1736.89,
305 | "y": 849.943,
306 | "width": 240.153,
307 | "height": 248.875,
308 | "seats": [
309 | {
310 | "x": 91.93,
311 | "y": 5.219999999999999
312 | },
313 | {
314 | "x": 91.93,
315 | "y": 135.56
316 | },
317 | {
318 | "x": 162.53,
319 | "y": 75.77
320 | },
321 | {
322 | "x": 22.159999999999997,
323 | "y": 75.77
324 | },
325 | {
326 | "x": 22.159999999999997,
327 | "y": 5.219999999999999
328 | },
329 | {
330 | "x": 162.53,
331 | "y": 135.56
332 | }
333 | ]
334 | },
335 | {
336 | "id": "eleventh-table",
337 | "x": 712.49,
338 | "y": 1110.94,
339 | "width": 240.153,
340 | "height": 248.875,
341 | "seats": [
342 | {
343 | "x": 92.14,
344 | "y": 5.32
345 | },
346 | {
347 | "x": 92.14,
348 | "y": 146.87
349 | },
350 | {
351 | "x": 162.74,
352 | "y": 75.87
353 | },
354 | {
355 | "x": 22.369999999999997,
356 | "y": 75.87
357 | },
358 | {
359 | "x": 22.369999999999997,
360 | "y": 5.32
361 | },
362 | {
363 | "x": 162.74,
364 | "y": 146.87
365 | }
366 | ]
367 | },
368 | {
369 | "id": "twelfth-table",
370 | "x": 968.816,
371 | "y": 1110.94,
372 | "width": 240.153,
373 | "height": 248.875,
374 | "seats": [
375 | {
376 | "x": 90.8,
377 | "y": 12.240000000000002
378 | },
379 | {
380 | "x": 90.8,
381 | "y": 141.47
382 | },
383 | {
384 | "x": 168.91,
385 | "y": 77.22
386 | },
387 | {
388 | "x": 17.509999999999998,
389 | "y": 77.22
390 | },
391 | {
392 | "x": 17.509999999999998,
393 | "y": 12.240000000000002
394 | },
395 | {
396 | "x": 168.91,
397 | "y": 141.47
398 | }
399 | ]
400 | },
401 | {
402 | "id": "thirteenth-table",
403 | "x": 1225.82,
404 | "y": 1110.94,
405 | "width": 240.153,
406 | "height": 248.875,
407 | "seats": [
408 | {
409 | "x": 92.2,
410 | "y": 4.960000000000001
411 | },
412 | {
413 | "x": 92.2,
414 | "y": 146.51
415 | },
416 | {
417 | "x": 162.8,
418 | "y": 75.51
419 | },
420 | {
421 | "x": 22.43,
422 | "y": 75.51
423 | },
424 | {
425 | "x": 22.43,
426 | "y": 4.960000000000001
427 | },
428 | {
429 | "x": 162.8,
430 | "y": 146.51
431 | }
432 | ]
433 | },
434 | {
435 | "id": "fourteenth-table",
436 | "x": 1482.82,
437 | "y": 1110.94,
438 | "width": 240.153,
439 | "height": 248.875,
440 | "seats": [
441 | {
442 | "x": 90.19,
443 | "y": 11.880000000000003
444 | },
445 | {
446 | "x": 90.19,
447 | "y": 141.11
448 | },
449 | {
450 | "x": 168.3,
451 | "y": 76.86
452 | },
453 | {
454 | "x": 16.89,
455 | "y": 76.86
456 | },
457 | {
458 | "x": 16.89,
459 | "y": 11.880000000000003
460 | },
461 | {
462 | "x": 168.3,
463 | "y": 141.11
464 | }
465 | ]
466 | },
467 | {
468 | "id": "fifteenth-table",
469 | "x": 1736.89,
470 | "y": 1110.94,
471 | "width": 240.153,
472 | "height": 248.875,
473 | "seats": [
474 | {
475 | "x": 91.93,
476 | "y": 5.229999999999997
477 | },
478 | {
479 | "x": 91.93,
480 | "y": 146.78
481 | },
482 | {
483 | "x": 162.53,
484 | "y": 75.78
485 | },
486 | {
487 | "x": 22.159999999999997,
488 | "y": 75.78
489 | },
490 | {
491 | "x": 22.159999999999997,
492 | "y": 5.229999999999997
493 | },
494 | {
495 | "x": 162.53,
496 | "y": 146.78
497 | }
498 | ]
499 | },
500 | {
501 | "id": "left-top-table",
502 | "x": 407.133,
503 | "y": 534.854,
504 | "width": 225.347,
505 | "height": 241.998,
506 | "seats": [
507 | {
508 | "x": 97.37,
509 | "y": 11.71
510 | },
511 | {
512 | "x": 33.55,
513 | "y": 67.04
514 | },
515 | {
516 | "x": 33.62,
517 | "y": 128.86
518 | },
519 | {
520 | "x": 97.37,
521 | "y": 173.15
522 | },
523 | {
524 | "x": 161.37,
525 | "y": 67.04
526 | },
527 | {
528 | "x": 161.37,
529 | "y": 128.86
530 | }
531 | ]
532 | },
533 | {
534 | "id": "right-top-table",
535 | "x": 2063.15,
536 | "y": 534.078,
537 | "width": 225.347,
538 | "height": 241.998,
539 | "seats": [
540 | {
541 | "x": 72.33,
542 | "y": 12.420000000000002
543 | },
544 | {
545 | "x": 136.14,
546 | "y": 67.75
547 | },
548 | {
549 | "x": 136.08,
550 | "y": 129.58
551 | },
552 | {
553 | "x": 72.33,
554 | "y": 173.87
555 | },
556 | {
557 | "x": 7.350000000000001,
558 | "y": 67.75
559 | },
560 | {
561 | "x": 7.350000000000001,
562 | "y": 129.58
563 | }
564 | ]
565 | },
566 | {
567 | "id": "left-bottom-table",
568 | "x": 407.133,
569 | "y": 948.32,
570 | "width": 225.347,
571 | "height": 241.998,
572 | "seats": [
573 | {
574 | "x": 97.37,
575 | "y": 11.689999999999998
576 | },
577 | {
578 | "x": 33.55,
579 | "y": 67.02
580 | },
581 | {
582 | "x": 33.62,
583 | "y": 128.85
584 | },
585 | {
586 | "x": 97.37,
587 | "y": 173.14
588 | },
589 | {
590 | "x": 161.37,
591 | "y": 67.02
592 | },
593 | {
594 | "x": 161.37,
595 | "y": 128.85
596 | }
597 | ]
598 | },
599 | {
600 | "id": "right-bottom-table",
601 | "x": 2063.15,
602 | "y": 947.545,
603 | "width": 225.347,
604 | "height": 241.998,
605 | "seats": [
606 | {
607 | "x": 72.33,
608 | "y": 12.399999999999999
609 | },
610 | {
611 | "x": 136.14,
612 | "y": 67.74
613 | },
614 | {
615 | "x": 136.08,
616 | "y": 129.55
617 | },
618 | {
619 | "x": 72.33,
620 | "y": 173.85
621 | },
622 | {
623 | "x": 7.350000000000001,
624 | "y": 67.74
625 | },
626 | {
627 | "x": 7.350000000000001,
628 | "y": 129.55
629 | }
630 | ]
631 | }
632 | ]
633 | }
634 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "include": ["src/**/*"],
3 | "compilerOptions": {
4 | "baseUrl": "./src",
5 | "jsx": "react",
6 | "lib": [
7 | "es2017",
8 | "dom"
9 | ],
10 | "module": "commonjs",
11 | "removeComments": true,
12 | "strict": true,
13 | "experimentalDecorators": false,
14 | "noEmitHelpers": true,
15 | "importHelpers": true,
16 | "keyofStringsOnly": true,
17 | "strictPropertyInitialization": false,
18 | "downlevelIteration": true,
19 | "skipLibCheck": true,
20 | "esModuleInterop": true,
21 | "resolveJsonModule": true
22 | },
23 | "compileOnSave": false
24 | }
25 |
--------------------------------------------------------------------------------