├── .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 | ![Map](https://github.com/YanLobat/effector-workshop/blob/master/Map-with%20users.png) 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 | 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 |
{firstTable.id}
24 |
25 |
26 | Conference background 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 | --------------------------------------------------------------------------------