Compare commits
629 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 901280363e | |||
| 6612f91047 | |||
| 5e7fabe1ce | |||
| e05aadd5c3 | |||
| 567a282dcf | |||
| 3eb5f6852f | |||
| 7ddab7d95c | |||
| 400340aaef | |||
| 5a43a2a9e1 | |||
| 69848dbe38 | |||
| ad40f73997 | |||
| fbdc460ee0 | |||
| e30c7f8daf | |||
| 9a87bc6627 | |||
| a2042e48e7 | |||
| cc02119938 | |||
| b2affbd87b | |||
| 0526286554 | |||
| 2b5e024bd0 | |||
| ed74d6f882 | |||
| ff2225a47b | |||
| 23832d2c4f | |||
| 655bce3fca | |||
| 1c33d5f7cf | |||
| 3737e34f32 | |||
| 36644308b8 | |||
| 080deea1e5 | |||
| a0caba4009 | |||
| 3c1deffbf1 | |||
| c07482e340 | |||
| cc80821658 | |||
| b0cbafdf44 | |||
| 6869085f6d | |||
| 9f8e61069c | |||
| 0b6b8451d1 | |||
| 17041758cb | |||
| ddfbd344f6 | |||
| 64d750fb9e | |||
| ebe9f62a8f | |||
| fd8cb20f52 | |||
| 7526059543 | |||
| d0bd5103cf | |||
| c9a3ae62ce | |||
| 7d2ca2bad3 | |||
| ab59d8872d | |||
| 4da821ce79 | |||
| 501af4b3f6 | |||
| 3c61032cf0 | |||
| af4e3207b4 | |||
| 3b835a4461 | |||
| f5a1fce6ac | |||
| 604b030045 | |||
| 9857a2363f | |||
| 892084cd41 | |||
| 47fca4b73a | |||
| d1c865dbd8 | |||
| f35b9d2037 | |||
| a4e039706d | |||
| 13f4790a36 | |||
| 829b1476d6 | |||
| 5c579cb592 | |||
| 2464d4b6bc | |||
| cb6729aa77 | |||
| d396fcc83a | |||
| d888c173f4 | |||
| 9d0f25984e | |||
| f2550a2df7 | |||
| 8f034b61de | |||
| 53ccf11d87 | |||
| fcfae5408b | |||
| 370e58494c | |||
| 9d5fbedb2c | |||
| cf7d4fc6bc | |||
| a960426a6d | |||
| 87862a26df | |||
| c29681cb7c | |||
| f01af4813e | |||
| de9a4f7b38 | |||
| b394f5fa5d | |||
| 9d9c777c93 | |||
| bddf254005 | |||
| 1a90223e22 | |||
| b963814067 | |||
| 921030d6c0 | |||
| 80e03c29e9 | |||
| 6cb252e031 | |||
| bfbb31ce25 | |||
| 849e0a5b18 | |||
| 9f497b83d5 | |||
| 344bdca41c | |||
| a05780ee39 | |||
| f7ac5e669f | |||
| b28ec325b8 | |||
| 6a6bfa3e39 | |||
| 753c990fc6 | |||
| 8069a54d1a | |||
| 9e2e58d45c | |||
| e7c59003cf | |||
| 8c435db886 | |||
| 2d41de8568 | |||
| a4a5096eb8 | |||
| d7128d7ddc | |||
| e06b23e73a | |||
| 90aefa56c2 | |||
| 6ceae61685 | |||
| d8033ec4ef | |||
| b4b9a0f5c4 | |||
| d4933d9535 | |||
| 024c34753c | |||
| d348f0d618 | |||
| 4662d0b30d | |||
| e6b873b861 | |||
| 88d0f8a222 | |||
| 15b94ea080 | |||
| e4218c708e | |||
| 772aa99c21 | |||
| e1c6729d04 | |||
| 4bd910fe6b | |||
| fe1b554dce | |||
| a75b4509a0 | |||
| e36330bcc2 | |||
| 51b36943ef | |||
| 78f80b4d7b | |||
| 65beb482c2 | |||
| ec7cab4b98 | |||
| 2089bd5c76 | |||
| 36cfa25d09 | |||
| 2394319605 | |||
| fb002efaec | |||
| 4b49371b08 | |||
| b5261ff528 | |||
| 61f03f4208 | |||
| d140bfeb38 | |||
| edb1ba680a | |||
| b3f927a46f | |||
| 288b8b1f51 | |||
| 416eb4129c | |||
| 613f0e755f | |||
| ed8562f95b | |||
| c95775194d | |||
| 783b65b5e9 | |||
| 38fef5c514 | |||
| e6286c97e4 | |||
| 55a2979814 | |||
| b5dc00eb7b | |||
| 813c085e3b | |||
| 550688a99e | |||
| 061e756237 | |||
| d6091fb496 | |||
| 52221edbee | |||
| 4cf4347611 | |||
| 60c9fa9b6f | |||
| 80c48533d7 | |||
| 3dfd5d2c89 | |||
| ba4118be38 | |||
| 9967eab3c5 | |||
| 861a0bf45f | |||
| 1b7089a694 | |||
| d6e345b2f3 | |||
| d720b08ccb | |||
| b773d04e4d | |||
| bd247c9ebd | |||
| e08c0f3605 | |||
| f86fcfe3d8 | |||
| 6629cd8156 | |||
| 5a29559ee7 | |||
| eab5e57e4b | |||
| 90ce3b1e6d | |||
| 97a438ac39 | |||
| a804655113 | |||
| 079cc18f5f | |||
| 18abd46de6 | |||
| 4728107d9a | |||
| 52f5c361d2 | |||
| efcd8002d5 | |||
| 2d9017bd5d | |||
| 7637ffd11c | |||
| b7fa1c403e | |||
| 48936e7150 | |||
| 3dcd837a9e | |||
| a859e045cd | |||
| 3a4637bc1a | |||
| a230f02005 | |||
| 3c4e06a84e | |||
| 0c8b1b5fe5 | |||
| 74b4de0543 | |||
| 730a61cddb | |||
| df7f3604f9 | |||
| 15e74aab9f | |||
| 7dd7071a0a | |||
| 8393f22ea6 | |||
| 985ed33346 | |||
| c357846ecf | |||
| 7685f304bf | |||
| 327d9d05c4 | |||
| f577733522 | |||
| 2a82a3758e | |||
| 80f7ef2da4 | |||
| 1ac0b286a9 | |||
| 20805afd31 | |||
| 47376ff7b7 | |||
| a787a1f870 | |||
| 2ca49a9566 | |||
| a4961638f2 | |||
| 583ab9480f | |||
| 73bf25904f | |||
| 5206050f6a | |||
| 5ca4abd2ac | |||
| 9bfc3ac023 | |||
| 088ad46a2f | |||
| d115add2ea | |||
| 9cdb2889d5 | |||
| 5cd1391ade | |||
| e74e915060 | |||
| e94f20ef78 | |||
| 25a335f2f0 | |||
| 26d243f658 | |||
| 3c0702022d | |||
| 9bfe4fb198 | |||
| 90b56c0471 | |||
| 28bb8e5299 | |||
| a18accb39c | |||
| 092b6cb655 | |||
| 24d733b4f7 | |||
| e3170e07dd | |||
| bda41ff48b | |||
| bf9be45efd | |||
| 323fe4b2c1 | |||
| 81938e714d | |||
| 6ab1b74342 | |||
| 1bedb3e467 | |||
| b1cd82c346 | |||
| a8df08e2f2 | |||
| 6b2a99caee | |||
| fbaf665ce9 | |||
| 4cfd8bde6f | |||
| b6e1a8b864 | |||
| 80eeb91f79 | |||
| 0063ef68f5 | |||
| eb6d281332 | |||
| ff0bb50414 | |||
| fb528ebc84 | |||
| a0504ec5fb | |||
| e40110a038 | |||
| 7512ed04e2 | |||
| 5c23dc39bf | |||
| 5488844845 | |||
| 33670ee1d6 | |||
| f569559f56 | |||
| d4083e4701 | |||
| ee808cffb2 | |||
| 99f1c4d117 | |||
| a54e85d359 | |||
| d10d03e6bb | |||
| 1d959f88f2 | |||
| 31d566a9c7 | |||
| 127e5e9d8d | |||
| c57b1402e9 | |||
| 3c759d3572 | |||
| 51deea06b8 | |||
| b9ba409c99 | |||
| 77d1f9954e | |||
| a3ad2c0030 | |||
| bc95306a15 | |||
| 45efd6b556 | |||
| 1ddbc65e14 | |||
| da90a3283a | |||
| efaf7ef6dd | |||
| 9999cc3417 | |||
| f931c25a9e | |||
| 0ac6abb5b5 | |||
| 79b8223c8c | |||
| 0633ece7ba | |||
| a474f0740f | |||
| 65e2ab90d2 | |||
| a7d31d6f4d | |||
| 042394d87d | |||
| 9d2fefe4ed | |||
| 3a08038da5 | |||
| 4cfad7bbca | |||
| cf8f8086d7 | |||
| 36f7d5698b | |||
| b0fd71ee0f | |||
| 0f8fe8cfd8 | |||
| c0e209ab67 | |||
| 6c091c3fac | |||
| 8a7b425e90 | |||
| 7ee7ab7e90 | |||
| ae603ba41d | |||
| 58c36dbf2d | |||
| d70ab851aa | |||
| de57d33a17 | |||
| 3caa419579 | |||
| fd9bc060cc | |||
| d705f062ed | |||
| a15a2ad831 | |||
| 4372c7a1e5 | |||
| d0dd09cce6 | |||
| db5670ffe2 | |||
| dd6e2cdf4b | |||
| 154fe260dc | |||
| 583e568085 | |||
| 3a137d4282 | |||
| 9c47f93e17 | |||
| 2b585216da | |||
| 77be2fd0c9 | |||
| 96ccc64eae | |||
| 9e9527686f | |||
| 6b0cb02afc | |||
| 4f5b03cdff | |||
| 63042e0e83 | |||
| 35ef8f28c0 | |||
| a9c3d382cf | |||
| de45ba36c6 | |||
| 7b227fb1f5 | |||
| b2e75da6f8 | |||
| 7aaf059e55 | |||
| 608283a6e6 | |||
| 9235571dcb | |||
| 5e806b6c64 | |||
| 989d215ef8 | |||
| 9daacb05d6 | |||
| b152cb5aea | |||
| 9ec2bf5cb6 | |||
| b8b18d5403 | |||
| cfc895b5dd | |||
| 287170ac96 | |||
| 6b636d0af3 | |||
| bd0e394d8a | |||
| 7f3764ae67 | |||
| 7363e9133b | |||
| 4d17137922 | |||
| fa24a9b292 | |||
| 4d637240aa | |||
| 2b617226f6 | |||
| 988440f5fd | |||
| 9d0d9f04ff | |||
| ab76048563 | |||
| 62e8f483c9 | |||
| 0ab872a2db | |||
| ed38d28fc5 | |||
| 2368f3f520 | |||
| 7a861a77d1 | |||
| f21bbadb5b | |||
| beab21d889 | |||
| c77ec3c9b0 | |||
| d8c289a6a8 | |||
| 77ba7c5f94 | |||
| 39baa0ab66 | |||
| 1c05c16041 | |||
| 62d13d66e5 | |||
| 1db0fab8e1 | |||
| 7e39a35997 | |||
| 8f97f706f0 | |||
| f5a6e86c61 | |||
| 9eeee67f03 | |||
| 314407806e | |||
| 9b196e3106 | |||
| c380260645 | |||
| 06d620ecba | |||
| ba41a79caa | |||
| 0c443bc341 | |||
| 9db04e1f5b | |||
| 1be8ac03fe | |||
| dfaeb05bf4 | |||
| 0b0a94e98b | |||
| 8878c1c57a | |||
| afe458cefd | |||
| bf4fa0dd30 | |||
| 9407dab844 | |||
| e34cc49a4f | |||
| 1de11320fd | |||
| dffd974ba6 | |||
| 7f70c3951e | |||
| d8b66eda0d | |||
| 42ad13bb93 | |||
| 1bc5a9c2c8 | |||
| c3517cdced | |||
| baf8c7cea0 | |||
| 3d4d90e46b | |||
| c46cddcdaf | |||
| c5e631064a | |||
| e5bdbe0bc8 | |||
| 14e216d448 | |||
| 1a7d811e8a | |||
| af478d49bc | |||
| 902665f0af | |||
| c042ae0ef7 | |||
| 988f179f14 | |||
| 77ad925b02 | |||
| d1a6aef77f | |||
| 2daa191b90 | |||
| b02dcb5baf | |||
| acc007ca81 | |||
| 142f673a17 | |||
| 40725cef90 | |||
| 389d2f4fac | |||
| d70d27299c | |||
| a6b8a7f555 | |||
| 518e5e75dc | |||
| 91b056274e | |||
| ce9dbdfb1c | |||
| 878335ef6d | |||
| 064979dd90 | |||
| 87d283763b | |||
| 5ae3750c3f | |||
| ebbb380c71 | |||
| f430471cd4 | |||
| 4bca554c4f | |||
| 61f547494b | |||
| 3c74d6e809 | |||
| 8045f38eac | |||
| 94dd549943 | |||
| ae47b6a838 | |||
| 9de0a394d7 | |||
| ce29a6438d | |||
| ef93e66968 | |||
| e32903baa5 | |||
| 753e76ccb1 | |||
| 7a523c50a0 | |||
| 7f5e323cd9 | |||
| b9e6c2ad21 | |||
| b42ad17f06 | |||
| ca8999a2d1 | |||
| bbe4a2eaa7 | |||
| ee6923c0a7 | |||
| b97ff9cc7c | |||
| 64f1c40594 | |||
| 13b55f50d7 | |||
| 2bbbcbae82 | |||
| 0b49b1a07b | |||
| eb08fffb3e | |||
| 10180345d3 | |||
| dbeb6bac94 | |||
| bdfa5d911c | |||
| cb71bd841c | |||
| 3d9a4fb063 | |||
| 736b83d8d3 | |||
| 20b7ac31f4 | |||
| 9a7f9c3993 | |||
| 13fb868117 | |||
| 360bd1d60f | |||
| 4a75e4de1e | |||
| 55247fde6b | |||
| 0b2dd9a546 | |||
| 69906acd22 | |||
| 985ef46648 | |||
| e0af97ac41 | |||
| 73b5f427cd | |||
| 9e1449e7fa | |||
| 007fdd2fac | |||
| dc62eac5e4 | |||
| b921d3eb46 | |||
| c50f6fe858 | |||
| 7e08ee6b6f | |||
| 29e1c2550f | |||
| a96f83dca5 | |||
| dc8687b794 | |||
| c216aec6c0 | |||
| 97bb5d3a75 | |||
| 956deb7dc4 | |||
| 512e5cf74f | |||
| 8951d83423 | |||
| 3a0e9c1938 | |||
| eb27aa74ab | |||
| 20bf564648 | |||
| bda3eb3302 | |||
| 3ad44318b1 | |||
| f703a2bd8f | |||
| 55c921c214 | |||
| 6188dc60a8 | |||
| fd4bad6477 | |||
| 7fa2bb1e10 | |||
| 3c5b4f4f24 | |||
| dbcb06cac8 | |||
| af417f1b3d | |||
| 7d918ad5ec | |||
| 569f7aac9f | |||
| 92df989fe3 | |||
| ba116d0919 | |||
| 6e553077d2 | |||
| d2a87c0daa | |||
| abfe12415f | |||
| 57dac37437 | |||
| 4df163eb30 | |||
| 3b5bb425a1 | |||
| bbe9970485 | |||
| 037424f103 | |||
| 6f4530c63c | |||
| 551b6af2d5 | |||
| 570c8482f6 | |||
| fe84754a0e | |||
| 8cc797f2ca | |||
| 825e658401 | |||
| 416758c178 | |||
| 398b2ab85a | |||
| 40e272ade1 | |||
| fb3809039f | |||
| 199bfb3ccb | |||
| 7f571b3917 | |||
| d4640944ee | |||
| 61891d7cd7 | |||
| 035137df0f | |||
| 7354b66453 | |||
| 652d3bac27 | |||
| b6c35ba444 | |||
| 9bb874ae2f | |||
| 7ce22883d2 | |||
| 2d6210c1e7 | |||
| 8f6c22d109 | |||
| 2c4bff2e7d | |||
| 4f66501811 | |||
| 3ad52a1e0b | |||
| b3c335fb25 | |||
| f1383d875e | |||
| 648517180a | |||
| 539e2d301f | |||
| dfe4a9aeef | |||
| ddae8c9b82 | |||
| 0f1762312f | |||
| b22c9f7fbd | |||
| daeb381f8b | |||
| f19d065c37 | |||
| 258ad73daa | |||
| d2eb717aab | |||
| 860b420608 | |||
| 3f18f70649 | |||
| b6ab0bfd2b | |||
| b17f156f38 | |||
| 9a333d58c4 | |||
| 58c34e6c47 | |||
| 1869fa4a07 | |||
| 8a16b8a47a | |||
| 212a2a1afc | |||
| e4c2b7fe2c | |||
| 788c58ecad | |||
| 4860438487 | |||
| ec8d55640d | |||
| 1ac2510ca3 | |||
| 53a7045e43 | |||
| b368f477ca | |||
| 87346e9ac5 | |||
| 8565f2142e | |||
| 24b957365f | |||
| 39a86b5a62 | |||
| 6febb3a654 | |||
| 409c396e68 | |||
| ecf6605eb5 | |||
| d88c50e3bb | |||
| a1f8618843 | |||
| d9820e49e0 | |||
| 8b876369c3 | |||
| 86aa81bdcc | |||
| 23c38245a3 | |||
| 48b8851657 | |||
| 2ed985518d | |||
| 9d06e89f5f | |||
| 3a49f735b6 | |||
| 69b1e087f9 | |||
| 980fc787bc | |||
| 39f33690dc | |||
| 0f81c9bab8 | |||
| ad98b0513a | |||
| f891479456 | |||
| fcc5bcad86 | |||
| 9c4364f082 | |||
| ee8de0330e | |||
| 95d906066f | |||
| b42d5abbb1 | |||
| 5f886f2a10 | |||
| bfcf73ae74 | |||
| 769d07440d | |||
| 435e6ad725 | |||
| f569ff237e | |||
| b85a4c9085 | |||
| b3b953196e | |||
| baa0cfa8c1 | |||
| 9b82a9cf59 | |||
| 39cd6cba2a | |||
| 7b1aee8a44 | |||
| e04465a751 | |||
| 09b586d94d | |||
| 48ac5c7d2c | |||
| 43d9a05bd6 | |||
| 68f44ab006 | |||
| 554c641c33 | |||
| 633e89628c | |||
| 5012b1e97a | |||
| 6d72c814d0 | |||
| 57d1e641a5 | |||
| 0b392222cf | |||
| e05f5c2b8f | |||
| 9a3bd880fb | |||
| 07b5e79d2b | |||
| c1e3c17504 | |||
| 354fefdae6 | |||
| ba4aba6a6d | |||
| f392062ece | |||
| 927eb81017 | |||
| ee4ef59ed3 | |||
| 063fb19dbd | |||
| 9169ee38ad | |||
| e68b61ce35 | |||
| 1f901b9a0a | |||
| 3abfff129c | |||
| 1267aa15b1 | |||
| 60e22554a8 | |||
| 72d98e057c | |||
| 0706fda67c | |||
| 1bdbd2f77d | |||
| 4daa99a020 | |||
| 89221a04ed | |||
| 765075e99b | |||
| 245f09b20f | |||
| d2649fdc16 | |||
| 2c08e9e68b | |||
| 5009409a51 | |||
| a8e4e739a2 | |||
| e1579a9d1d | |||
| 21510e7b71 | |||
| 59782cda43 | |||
| 6947ba6b33 | |||
| 71ad5427cb | |||
| c14de8e119 | |||
| 7e67bdcb45 | |||
| 5a0267e20f | |||
| aa3ef3456d | |||
| 03c01d842d | |||
| 502490ced1 |
@@ -1,4 +0,0 @@
|
||||
/nextjs/node_modules/
|
||||
/node_modules/
|
||||
/nextjs/node_modules/
|
||||
/jenkins/
|
||||
Vendored
+3
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
/volume/
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
CLIENT=tt-app
|
||||
|
||||
DOCKERCOMPOSE := $(shell command -v docker-compose 2> /dev/null)
|
||||
USAGE := Usage: make ENV={prod|preprod|dev} {SYS=windows|linux} {docker-build|docker-start|docker-stop|docker-restart}
|
||||
|
||||
ifndef DOCKERCOMPOSE
|
||||
$(error docker-compose is not available)
|
||||
endif
|
||||
|
||||
ifndef ENV
|
||||
$(info Environment variable is not set)
|
||||
$(info $(USAGE))
|
||||
$(error Exiting)
|
||||
endif
|
||||
|
||||
SYS=linux
|
||||
|
||||
DC_OVERRIDE := $(shell test -e conf/docker-compose.override.yml && echo "--file conf/docker-compose.override.yml")
|
||||
DC_OVERRIDE_ENV := $(shell test -e conf/docker-compose.$(ENV).yml && echo "--file conf/docker-compose.$(ENV).yml")
|
||||
|
||||
ifeq ($(SYS),linux)
|
||||
DC=ENV=$(ENV) CLIENT=$(CLIENT) "$(DOCKERCOMPOSE)" --project-name "$(CLIENT)_$(ENV)" --file conf/docker-compose.yml $(DC_OVERRIDE) $(DC_OVERRIDE_ENV)
|
||||
else
|
||||
DC=ENV=$(ENV) CLIENT=$(CLIENT) "$(DOCKERCOMPOSE)" --project-name "$(CLIENT)_$(ENV)" --file conf/docker-compose.yml $(DC_OVERRIDE) $(DC_OVERRIDE_ENV)
|
||||
endif
|
||||
|
||||
|
||||
default:
|
||||
$(info $(USAGE))
|
||||
$(error Exiting)
|
||||
|
||||
docker-build:
|
||||
$(DC) build
|
||||
$(DC) up -d
|
||||
|
||||
docker-start:
|
||||
$(DC) start
|
||||
|
||||
docker-stop:
|
||||
$(DC) stop
|
||||
|
||||
docker-restart: docker-stop docker-start
|
||||
|
||||
docker-rm: docker-stop
|
||||
$(DC) rm
|
||||
|
||||
sys:
|
||||
@echo $(SYS)
|
||||
@echo $(DC)
|
||||
@@ -0,0 +1,28 @@
|
||||
## 1. Droits
|
||||
|
||||
mot de pass de depart (juste pour info, pas d'utilité)
|
||||
Jenkins : 8c628b7abfe242a397ac7fbedfd4de8c
|
||||
|
||||
Besoin d'effectuer la commande sudo chmod 666 /var/run/docker.sock dans le container Jenkins.
|
||||
docker exec -it --user root $(containerName) /bin/bash
|
||||
|
||||
docker exec -it -u 0 60344f0b414b bash
|
||||
|
||||
## 2. post install
|
||||
Installer yarn
|
||||
|
||||
## 3. run
|
||||
localhost:8080
|
||||
|
||||
>stop curent TTapp dev run to free 3000 port
|
||||
|
||||
### 1. login:password
|
||||
>sglatz:yVqSfBDRPp07
|
||||
|
||||
### 2. gitea password
|
||||
>tt-jenkins:
|
||||
|
||||
#### 2. As Kanye West said:
|
||||
|
||||
> We're living the future so
|
||||
> the present is our past.
|
||||
@@ -0,0 +1,17 @@
|
||||
version: '2'
|
||||
services:
|
||||
|
||||
jenkins:
|
||||
hostname: jenkins
|
||||
image: jenkinsci/blueocean
|
||||
ports:
|
||||
- '8080:8080'
|
||||
- '50000:50000'
|
||||
privileged: true
|
||||
volumes:
|
||||
# enable persistent volume (warning: make sure that the local jenkins_home folder is created)
|
||||
- '../volume/jenkins_data:/var/jenkins_home'
|
||||
- '/var/run/docker.sock:/var/run/docker.sock'
|
||||
restart: always
|
||||
volumes:
|
||||
jenkins_data:
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"cypress": "^3.4.1"
|
||||
}
|
||||
}
|
||||
+1217
File diff suppressed because it is too large
Load Diff
-25
@@ -1,25 +0,0 @@
|
||||
SERVER_URL=http://localhost:80
|
||||
MONGO_URI=mongodb://declic:declic42@ds115653.mlab.com:15653/next-auth
|
||||
FACEBOOK_ID=2332071840142634
|
||||
FACEBOOK_SECRET=afb8bbca72bd8c517c64f81e998e4f12
|
||||
PROD_FACEBOOK_ID=1800752833559014
|
||||
PROD_FACEBOOK_SECRET=454de729b1e58f506f94c8b50ca5c011
|
||||
TT_LINKEDIN_ID=86m6hegwyj8lbo
|
||||
TT_LINKEDIN_SECRET=LIOibqDszsP2vQSr
|
||||
LINKEDIN_ID=77k1m8qx7w6ygj
|
||||
LINKEDIN_SECRET=mbcnVjyy2vqAZz1B
|
||||
GOOGLE_ID=
|
||||
GOOGLE_SECRET=
|
||||
TWITTER_KEY=
|
||||
TWITTER_SECRET=
|
||||
EMAIL_FROM=contact@application.bzh
|
||||
EMAIL_SERVER=mail.gandi.net
|
||||
EMAIL_PORT=587
|
||||
EMAIL_SECURE=true
|
||||
EMAIL_USERNAME=noreply@application.bzh
|
||||
EMAIL_PASSWORD=PWApwaPWApwa56!!!
|
||||
FIREBASE_API_KEY=AAAAHlF5rsI:APA91bHXvjGDJRLzVvqmBDPM1Et96Bw0Iuvc7EGe9wt4a_lSJijDlbZ3RT96seLDK-QM6Mf8qQ4dH6MI9iWt1_d3uzKG2bxr56zltglZ3Z9xU8gz2iJeRGmlOd0YdV43IumIVe6dIOr-
|
||||
RECAPTCHA_VERIFY_URL=https://www.google.com/recaptcha/api/siteverify
|
||||
RECAPTCHA_SECRET=6LcCvHcUAAAAAIG-_iR5AXqkwxINfgqxKZ_kG244
|
||||
MJ_APIKEY_PUBLIC=355697fac93fca865660734b4fdffaeb
|
||||
MJ_APIKEY_PRIVATE=ddecf75321ca89c09e561857b13534c8
|
||||
+6
-2
@@ -1,4 +1,8 @@
|
||||
|
||||
/node_modules/
|
||||
.next/
|
||||
.gitignore
|
||||
.env
|
||||
ecosystem.config.js
|
||||
yarn-error.log
|
||||
robots.txt
|
||||
/out/
|
||||
yarn.lock
|
||||
@@ -0,0 +1,18 @@
|
||||
FROM keymetrics/pm2:8-alpine
|
||||
|
||||
ENV TERM=xterm
|
||||
|
||||
|
||||
# RUN pm2 -V
|
||||
RUN apk --no-cache update \
|
||||
&& apk --no-cache add g++ make bash zlib-dev libpng-dev \
|
||||
&& rm -fr /var/cache/apk/*
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
RUN apk add --no-cache yarn
|
||||
|
||||
CMD cd /home/app/nextjs && yarn cache dir && yarn cache clean && yarn install && yarn build && pm2 startOrReload ecosystem.config.js --env production --no-daemon
|
||||
|
||||
Vendored
+43
@@ -0,0 +1,43 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'mhart/alpine-node:latest'
|
||||
args '-p 3000:3000'
|
||||
}
|
||||
// dockerfile true
|
||||
}
|
||||
environment {
|
||||
CI = 'true'
|
||||
}
|
||||
stages {
|
||||
stage('Prepare') {
|
||||
steps {
|
||||
// withEnv(['PATH+NODE=/usr/local/bin/node']) {
|
||||
// echo 'Prepare step started ... '
|
||||
// sh 'echo prefix = ~/.node >> ~/.npmrc'
|
||||
// sh 'whoami'
|
||||
// sh 'chown -R node:node /usr/local/lib/node_modules'
|
||||
sh 'cd /var/jenkins_home/workspace/tt-app_app_Saga/nextjs/'
|
||||
sh 'yarn install'
|
||||
// }
|
||||
}
|
||||
}
|
||||
stage('Build') {
|
||||
steps {
|
||||
echo 'Build step started ... '
|
||||
sh '/var/jenkins_home/workspace/tt-app_app_Saga/nextjs/scripts/start.sh'
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh '/var/jenkins_home/workspace/tt-app_app_Saga/nextjs/scripts/test.sh'
|
||||
}
|
||||
}
|
||||
stage('Response') {
|
||||
steps {
|
||||
echo 'Vous pouvez pull sur la production'
|
||||
sh '/var/jenkins_home/workspace/tt-app_app_Saga/nextjs/scripts/kill.sh'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
-1
@@ -2,7 +2,7 @@
|
||||
|
||||
## How to use
|
||||
|
||||
Download the example [or clone the repo](https://github.com/mui-org/material-ui):
|
||||
Download the example [or clone the repo](https://git.e-declic.net/talents-tube/app.git):
|
||||
|
||||
```bash
|
||||
git clone
|
||||
@@ -12,6 +12,7 @@ Install it and run:
|
||||
|
||||
```bash
|
||||
yarn install
|
||||
yarn build
|
||||
yarn dev
|
||||
```
|
||||
|
||||
@@ -24,3 +25,43 @@ yarn dev
|
||||
[Redux-Saga]
|
||||
https://redux-saga.js.org/docs/basics/ErrorHandling.html
|
||||
is a library that aims to make application side effect asynchronous things like data fetching and impure things like accessing the browser cache
|
||||
|
||||
## Integration
|
||||
### Naming conventions
|
||||
style classes name syntax : Camel case
|
||||
Try to name without too much precision
|
||||
|
||||
|
||||
### Material UI
|
||||
|
||||
[components/atoms/theme.js] Const file
|
||||
[components/atoms/] Atomic components
|
||||
[components/organisms] General components
|
||||
[components/templates] Layouts components
|
||||
|
||||
## MEP
|
||||
|
||||
=================================
|
||||
Mise en PRE-production de l’application PWA
|
||||
=================================
|
||||
Aller dans la preproduction et git pull dans le répertoire /app (pas www)
|
||||
Possibilité de devoir git stash
|
||||
Attention de bien avoir le .env
|
||||
Restart docker preproduction (sudo make ENV=preprod docker-restart)
|
||||
sudo docker exec -it frontnginx.nginx bash
|
||||
nginx -t && nginx -s reload
|
||||
|
||||
## Test
|
||||
### Cypress
|
||||
|
||||
[Gmail-tester]
|
||||
https://github.com/levz0r/gmail-tester
|
||||
|
||||
## Continuous Integration
|
||||
### Jenkins
|
||||
|
||||
>in case of emergency
|
||||
make docker-restart ENV=dev
|
||||
|
||||
adduser jenkins root
|
||||
|
||||
|
||||
+29
-9
@@ -5,11 +5,26 @@ import {
|
||||
FETCH_AD_REQUEST,
|
||||
FETCH_AD_SUCCESS,
|
||||
FETCH_AD_FAILURE,
|
||||
FETCH_PROMOTED_ADS_REQUEST,
|
||||
FETCH_PROMOTED_ADS_SUCCESS,
|
||||
FETCH_PROMOTED_ADS_FAILURE,
|
||||
FETCH_EMPHASIS_ADS_REQUEST,
|
||||
FETCH_EMPHASIS_ADS_SUCCESS,
|
||||
FETCH_EMPHASIS_ADS_FAILURE,
|
||||
CLEAR_AD,
|
||||
COUNT_AD_VIEW,
|
||||
TOGGLE_PROMOTED_FILTER_ADS
|
||||
} from '../types'
|
||||
|
||||
//Ads stat count
|
||||
|
||||
export const countAdViewRequest = (id) => ({
|
||||
type: COUNT_AD_VIEW,
|
||||
id
|
||||
});
|
||||
|
||||
//Is ads promoted?
|
||||
|
||||
export const togglePromotedFilterRequest = () => ({
|
||||
type: TOGGLE_PROMOTED_FILTER_ADS
|
||||
});
|
||||
|
||||
//Ads list
|
||||
|
||||
@@ -30,18 +45,18 @@ export const fetchAdsRequest = (payload) => ({
|
||||
|
||||
//Ads list
|
||||
|
||||
export const fetchPromotedAdsRequest = (payload) => ({
|
||||
type: FETCH_PROMOTED_ADS_REQUEST,
|
||||
export const fetchEmphasisAdsRequest = (payload) => ({
|
||||
type: FETCH_EMPHASIS_ADS_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchPromotedAdsSuccess = res => ({
|
||||
type: FETCH_PROMOTED_ADS_SUCCESS,
|
||||
export const fetchEmphasisAdsSuccess = res => ({
|
||||
type: FETCH_EMPHASIS_ADS_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchPromotedAdsFailure = res => ({
|
||||
type: FETCH_PROMOTED_ADS_FAILURE,
|
||||
export const fetchEmphasisAdsFailure = res => ({
|
||||
type: FETCH_EMPHASIS_ADS_FAILURE,
|
||||
res
|
||||
});
|
||||
|
||||
@@ -60,4 +75,9 @@ export const fetchAdsRequest = (payload) => ({
|
||||
export const fetchAdFailure = res => ({
|
||||
type: FETCH_AD_FAILURE,
|
||||
res
|
||||
});
|
||||
|
||||
//clear ad
|
||||
export const clearAd = res => ({
|
||||
type: CLEAR_AD
|
||||
});
|
||||
@@ -0,0 +1,94 @@
|
||||
import {
|
||||
POST_AD_ANSWER_REQUEST,
|
||||
POST_AD_ANSWER_FAILURE,
|
||||
POST_AD_ANSWER_SUCCESS,
|
||||
FETCH_ADS_ANSWER_REQUEST,
|
||||
FETCH_ADS_ANSWER_SUCCESS,
|
||||
FETCH_ADS_ANSWER_FAILURE,
|
||||
FETCH_ANSWER_REQUEST,
|
||||
FETCH_ANSWER_SUCCESS,
|
||||
FETCH_ANSWER_FAILURE,
|
||||
POST_MESSAGE_ANSWER_REQUEST,
|
||||
POST_MESSAGE_ANSWER_FAILURE,
|
||||
POST_MESSAGE_ANSWER_SUCCESS,
|
||||
FETCH_ANSWER_MESAGES_REQUEST,
|
||||
FETCH_ANSWER_MESAGES_FAILURE,
|
||||
FETCH_ANSWER_MESAGES_SUCCESS,
|
||||
} from "../types";
|
||||
|
||||
export const postAdAnswerRequest = payload => ({
|
||||
type: POST_AD_ANSWER_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const postAdAnswerRequestFailure = errors => ({
|
||||
type: POST_AD_ANSWER_FAILURE,
|
||||
errors
|
||||
});
|
||||
|
||||
export const postAdAnswerRequestSuccess = res => ({
|
||||
type: POST_AD_ANSWER_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const postMessageAnswerRequest = payload => ({
|
||||
type: POST_MESSAGE_ANSWER_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const postMessageAnswerFailure = errors => ({
|
||||
type: POST_MESSAGE_ANSWER_FAILURE,
|
||||
errors
|
||||
});
|
||||
|
||||
export const postMessageAnswerSuccess = res => ({
|
||||
type: POST_MESSAGE_ANSWER_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
|
||||
export const fetchAdsAnsweredRequest = payload => ({
|
||||
type: FETCH_ADS_ANSWER_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchAdsAnsweredSuccess = res => ({
|
||||
type: FETCH_ADS_ANSWER_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
|
||||
export const fetchAdsAnsweredFailure = errors => ({
|
||||
type: FETCH_ADS_ANSWER_FAILURE,
|
||||
errors
|
||||
});
|
||||
|
||||
export const fetchAnswerRequest = payload => ({
|
||||
type: FETCH_ANSWER_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchAnswerSuccess = res => ({
|
||||
type: FETCH_ANSWER_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchAnswerFailure = errors => ({
|
||||
type: FETCH_ANSWER_FAILURE,
|
||||
errors
|
||||
});
|
||||
|
||||
export const fetchAnswerMessagesRequest = payload => ({
|
||||
type: FETCH_ANSWER_MESAGES_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchAnswerMessagesSuccess = res => ({
|
||||
type: FETCH_ANSWER_MESAGES_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchAnswerMessagesFailure = errors => ({
|
||||
type: FETCH_ANSWER_MESAGES_FAILURE,
|
||||
errors
|
||||
});
|
||||
+64
-13
@@ -2,22 +2,73 @@ import {
|
||||
FETCH_COMPANIES_REQUEST,
|
||||
FETCH_COMPANIES_SUCCESS,
|
||||
FETCH_COMPANIES_FAILURE,
|
||||
FETCH_COMPANY_REQUEST,
|
||||
FETCH_COMPANY_SUCCESS,
|
||||
FETCH_COMPANY_FAILURE,
|
||||
FETCH_EMPHASIS_COMPANIES_REQUEST,
|
||||
FETCH_EMPHASIS_COMPANIES_SUCCESS,
|
||||
FETCH_EMPHASIS_COMPANIES_FAILURE,
|
||||
CLEAR_COMPANY,
|
||||
COUNT_COMPANY_VIEW,
|
||||
} from '../types'
|
||||
|
||||
|
||||
//COMPANIES list
|
||||
|
||||
export const fetchCompaniesRequest = (payload) => ({
|
||||
type: FETCH_COMPANIES_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchCompaniesSuccess = res => ({
|
||||
type: FETCH_COMPANIES_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchCompaniesFailure = res => ({
|
||||
type: FETCH_COMPANIES_FAILURE,
|
||||
res
|
||||
});
|
||||
type: FETCH_COMPANIES_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchCompaniesSuccess = res => ({
|
||||
type: FETCH_COMPANIES_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchCompaniesFailure = res => ({
|
||||
type: FETCH_COMPANIES_FAILURE,
|
||||
res
|
||||
});
|
||||
|
||||
//COMPANY single
|
||||
export const countCompanyViewRequest = (id) => ({
|
||||
type: COUNT_COMPANY_VIEW,
|
||||
id
|
||||
});
|
||||
|
||||
export const fetchCompanyRequest = payload => ({
|
||||
type: FETCH_COMPANY_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchCompanySuccess = res => ({
|
||||
type: FETCH_COMPANY_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchCompanyFailure = res => ({
|
||||
type: FETCH_COMPANY_FAILURE,
|
||||
res
|
||||
});
|
||||
|
||||
//EMPHASIS 'aka mise en avant'
|
||||
|
||||
export const fetchEmphasisCompaniesRequest = (payload) => ({
|
||||
type: FETCH_EMPHASIS_COMPANIES_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchEmphasisCompaniesSuccess = res => ({
|
||||
type: FETCH_EMPHASIS_COMPANIES_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchEmphasisCompaniesFailure = res => ({
|
||||
type: FETCH_EMPHASIS_COMPANIES_FAILURE,
|
||||
res
|
||||
});
|
||||
|
||||
//clear ad
|
||||
export const clearCompany = res => ({
|
||||
type: CLEAR_COMPANY
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
UPLOAD_MEDIA_REQUEST,
|
||||
UPLOAD_MEDIA_FAILURE,
|
||||
UPLOAD_MEDIA_SUCCESS
|
||||
} from "../types";
|
||||
|
||||
export const uploadMedia = payload => ({
|
||||
type: UPLOAD_MEDIA_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const uploadMediaFailure = errors => ({
|
||||
type: UPLOAD_MEDIA_FAILURE,
|
||||
errors
|
||||
});
|
||||
|
||||
export const uploadMediaSuccess = res => ({
|
||||
type: UPLOAD_MEDIA_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
|
||||
|
||||
+127
-4
@@ -7,8 +7,45 @@ import {
|
||||
SUBSCRIBE_NEWSLETTER_FAILURE,
|
||||
REQUEST_REGISTER,
|
||||
REQUEST_REGISTER_SUCCESS,
|
||||
REQUEST_REGISTER_FAILURE
|
||||
REQUEST_REGISTER_FAILURE,
|
||||
ACCOUNT_CONFIRMATION_REQUEST,
|
||||
ACCOUNT_CONFIRMATION_SUCCESS,
|
||||
ACCOUNT_CONFIRMATION_FAILURE,
|
||||
SEND_CONFIRMATION_EMAIL_REQUEST,
|
||||
SEND_CONFIRMATION_EMAIL_SUCCESS,
|
||||
SEND_CONFIRMATION_EMAIL_FAILURE,
|
||||
UPDATE_USER_REQUEST,
|
||||
UPDATE_USER_SUCCESS,
|
||||
UPDATE_USER_FAILURE,
|
||||
RESET_PASSWORD_REQUEST,
|
||||
RESET_PASSWORD_SUCCESS,
|
||||
RESET_PASSWORD_FAILURE,
|
||||
SUBSCRIBE_USER_TO_PUSH_REQUEST,
|
||||
SUBSCRIBE_USER_TO_PUSH_SUCCESS,
|
||||
SUBSCRIBE_USER_TO_PUSH_FAILURE,
|
||||
FETCH_USER_SUBSCRIPTION_TO_PUSH_REQUEST,
|
||||
FETCH_USER_SUBSCRIPTION_TO_PUSH_SUCCESS,
|
||||
FETCH_USER_SUBSCRIPTION_TO_PUSH_FAILURE,
|
||||
UPDATE_USER_PICTURE_REQUEST,
|
||||
UPDATE_USER_PICTURE_SUCCESS,
|
||||
UPDATE_USER_PICTURE_FAILURE
|
||||
} from '../types'
|
||||
|
||||
|
||||
export const resetPasswordRequest = (payload) => ({
|
||||
type: RESET_PASSWORD_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const resetPasswordSuccess = () => ({
|
||||
type: RESET_PASSWORD_SUCCESS,
|
||||
});
|
||||
|
||||
export const resetPasswordFailure = (err) => ({
|
||||
type: RESET_PASSWORD_FAILURE,
|
||||
err
|
||||
});
|
||||
|
||||
export const fetchCurrentUserRequest = () => ({
|
||||
type: FETCH_CURRENT_USER_REQUEST
|
||||
});
|
||||
@@ -30,7 +67,7 @@ export const registerUserRequest = (payload) => ({
|
||||
|
||||
export const registerUserSuccess = () => ({
|
||||
type: REQUEST_REGISTER_SUCCESS,
|
||||
|
||||
|
||||
});
|
||||
|
||||
export const registerUserFailure = (err) => ({
|
||||
@@ -38,6 +75,20 @@ export const registerUserFailure = (err) => ({
|
||||
err
|
||||
});
|
||||
|
||||
export const sendConfirmationEmailRequest = (payload) => ({
|
||||
type: SEND_CONFIRMATION_EMAIL_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const sendConfirmationEmailSuccess = (payload) => ({
|
||||
type: SEND_CONFIRMATION_EMAIL_SUCCESS,
|
||||
});
|
||||
|
||||
export const sendConfirmationEmailFailure = (err) => ({
|
||||
type: SEND_CONFIRMATION_EMAIL_FAILURE,
|
||||
err
|
||||
});
|
||||
|
||||
export const subscribeToNewsletterRequest = (payload) => ({
|
||||
type: SUBSCRIBE_NEWSLETTER_REQUEST,
|
||||
payload
|
||||
@@ -47,6 +98,78 @@ export const subscribeToNewsletterSuccess = () => ({
|
||||
type: SUBSCRIBE_NEWSLETTER_SUCCESS,
|
||||
});
|
||||
|
||||
export const subscribeToNewsletterFailure = () => ({
|
||||
export const subscribeToNewsletterFailure = (err) => ({
|
||||
type: SUBSCRIBE_NEWSLETTER_FAILURE,
|
||||
});
|
||||
err
|
||||
});
|
||||
|
||||
export const requestConfirmationAccountRequest = (payload) => ({
|
||||
type: ACCOUNT_CONFIRMATION_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const requestConfirmationAccountSuccess = () => ({
|
||||
type: ACCOUNT_CONFIRMATION_SUCCESS,
|
||||
});
|
||||
|
||||
export const requestConfirmationAccountFailure = (err) => ({
|
||||
type: ACCOUNT_CONFIRMATION_FAILURE,
|
||||
err
|
||||
});
|
||||
|
||||
export const updateUserRequest = (payload) => ({
|
||||
type: UPDATE_USER_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const updateUserRequestSuccess = () => ({
|
||||
type: UPDATE_USER_SUCCESS,
|
||||
});
|
||||
|
||||
export const updateUserRequestFailure = (err) => ({
|
||||
type: UPDATE_USER_FAILURE,
|
||||
err
|
||||
});
|
||||
|
||||
export const subscribeUserToPushRequest = (payload) => ({
|
||||
type: SUBSCRIBE_USER_TO_PUSH_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const subscribeUserToPushSuccess = () => ({
|
||||
type: SUBSCRIBE_USER_TO_PUSH_SUCCESS,
|
||||
});
|
||||
|
||||
export const subscribeUserToPushFailure = (err) => ({
|
||||
type: SUBSCRIBE_USER_TO_PUSH_FAILURE,
|
||||
err
|
||||
});
|
||||
|
||||
export const fetchUserSubscriptionToPushRequest = (payload) => ({
|
||||
type: FETCH_USER_SUBSCRIPTION_TO_PUSH_REQUEST,
|
||||
payload
|
||||
});
|
||||
|
||||
export const fetchUserSubscriptionToPushSuccess = (res) => ({
|
||||
type: FETCH_USER_SUBSCRIPTION_TO_PUSH_SUCCESS,
|
||||
res
|
||||
});
|
||||
|
||||
export const fetchUserSubscriptionToPushFailure = (err) => ({
|
||||
type: FETCH_USER_SUBSCRIPTION_TO_PUSH_FAILURE,
|
||||
err
|
||||
});
|
||||
export const updateUserPictureRequest = (payload) => ({
|
||||
type: UPDATE_USER_PICTURE_REQUEST,
|
||||
payload
|
||||
})
|
||||
|
||||
export const updateUserPictureSuccess = (data) => ({
|
||||
type: UPDATE_USER_PICTURE_SUCCESS,
|
||||
data
|
||||
})
|
||||
|
||||
export const updateUserPictureFailure = (err) => ({
|
||||
type: UPDATE_USER_PICTURE_FAILURE,
|
||||
err
|
||||
})
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { withRouter } from 'next/router';
|
||||
import Link from 'next/link';
|
||||
import React, { Children } from 'react';
|
||||
|
||||
const ActiveLink = ({ router, children, ...props }) => {
|
||||
const child = Children.only(children);
|
||||
|
||||
let className = child.props.className || '';
|
||||
if (router.pathname === props.href && props.activeClassName) {
|
||||
className = `${className} ${props.activeClassName}`.trim();
|
||||
}
|
||||
|
||||
delete props.activeClassName;
|
||||
|
||||
return <Link {...props}>{React.cloneElement(child, { className })}</Link>;
|
||||
};
|
||||
|
||||
export default withRouter(ActiveLink);
|
||||
@@ -0,0 +1,84 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import VideoVimeo from './VideoVimeo'
|
||||
import ChatCardMessage from './ChatCardMessage'
|
||||
import _ from "lodash"
|
||||
import Ar from "lodash/array"
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
card: {
|
||||
width: '100%',
|
||||
},
|
||||
userMessage: {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
messageSender: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '300',
|
||||
color: theme.palette.primary.important,
|
||||
},
|
||||
messageContent: {
|
||||
fontWeight: '300',
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
messageDate: {
|
||||
fontSize: '0.8em',
|
||||
fontWeight: '300',
|
||||
color: theme.palette.primary.dark,
|
||||
padding: theme.spacing(2,0),
|
||||
},
|
||||
currentUserBubble: {
|
||||
background: theme.palette.primary.important,
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
currentMessageSender: {
|
||||
color: theme.palette.primary.important,
|
||||
},
|
||||
otherUserBubble: {
|
||||
background: theme.palette.primary.main,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
otherMessageSender: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
avatar: {
|
||||
width: '2.5em',
|
||||
borderRadius: '50%',
|
||||
margin: theme.spacing(0,1),
|
||||
},
|
||||
videoVimeo: {
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingBottom: '55%',
|
||||
},
|
||||
|
||||
},
|
||||
}));
|
||||
|
||||
export default function ChatCard(props) {
|
||||
const classes = useStyles();
|
||||
const userFirstName = useSelector(state => state.user.firstname);
|
||||
const userLastName = useSelector(state => state.user.lastname);
|
||||
const userPicture = useSelector(state => state.user.picture);
|
||||
const bubbleClass = (userFirstName === props.message.sender.firstname && userLastName === props.message.sender.lastname) ? classes.currentUserBubble : classes.otherUserBubble
|
||||
const messageSenderClass = (userFirstName === props.message.sender.firstname && userLastName === props.message.sender.lastname) ? classes.currentMessageSender : classes.otherMessageSender
|
||||
const bubbleSide = (userFirstName === props.message.sender.firstname && userLastName === props.message.sender.lastname) ? 'right' : 'left'
|
||||
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ChatCardMessage side={bubbleSide} firstname={props.message.sender.firstname} lastname={props.message.sender.lastname} picture={userPicture} video={(props.message.video) ? true : false} message={(props.message.video) ? (<VideoVimeo classesOverride={ classes.videoVimeo } videoId={Ar.last(_.split(props.message.video.cdn_url, '/'))} />) : props.message.content} messageSenderClass={messageSenderClass} bubbleClass={bubbleClass}/>
|
||||
|
||||
<Typography
|
||||
className={classes.messageDate}
|
||||
align="center"
|
||||
>
|
||||
Envoyée le {(new Date(props.message.c_time)).toLocaleDateString("fr-FR")} à {(new Date(props.message.c_time)).toLocaleTimeString("fr-FR")}
|
||||
</Typography>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from 'react'
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import _ from "lodash"
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
userMessageLeft: {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
userMessageRight: {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
messageSender: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '300',
|
||||
color: theme.palette.primary.important,
|
||||
},
|
||||
messageContent: {
|
||||
fontWeight: '300',
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
userBubble: {
|
||||
width: 'auto',
|
||||
borderRadius: '5px',
|
||||
padding: theme.spacing(2),
|
||||
wordBreak: 'break-word',
|
||||
},
|
||||
userBubbleVideo: {
|
||||
minWidth: '60vw',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
minWidth: '45vw',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
minWidth: '30vw',
|
||||
},
|
||||
},
|
||||
currentUserBubble: {
|
||||
background: theme.palette.primary.main,
|
||||
alignSelf: 'flex-end',
|
||||
},
|
||||
otherUserBubble: {
|
||||
background: theme.palette.primary.important,
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
avatar: {
|
||||
width: '2.5em',
|
||||
height: '2.5em',
|
||||
borderRadius: '50%',
|
||||
margin: theme.spacing(0,1),
|
||||
},
|
||||
first: {
|
||||
order: 1,
|
||||
},
|
||||
second: {
|
||||
order: 2,
|
||||
}
|
||||
}));
|
||||
|
||||
export default function ChatCardMessage(props) {
|
||||
const { side, firstname, lastname, picture, message, video, messageSenderClass, bubbleClass } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
const userBubble = (video) ? classes.userBubble +' '+ classes.userBubbleVideo : classes.userBubble
|
||||
return (
|
||||
<section className={(side==='left')? classes.userMessageLeft : classes.userMessageRight }>
|
||||
|
||||
{picture ?
|
||||
<img alt="talents-tube" src={`http://localhost/uploads/img/${picture}`} className={(side==='left')? classes.first+' '+classes.avatar : classes.second +' '+classes.avatar} />
|
||||
:
|
||||
<img alt="talents-tube" src='/static/images/default/account-avatar-default.svg' className={(side==='left')? classes.first +' '+ classes.avatar : classes.second+' '+classes.avatar } />
|
||||
}
|
||||
<div className={(side==='left')? classes.second : classes.first}>
|
||||
<Typography
|
||||
className={classes.messageSender +' '+ messageSenderClass}
|
||||
>
|
||||
{firstname + ' ' + lastname}
|
||||
</Typography>
|
||||
<div className={userBubble + ' ' + bubbleClass}>
|
||||
<Typography
|
||||
className={classes.messageContent}
|
||||
>
|
||||
{message}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from "react";
|
||||
import { Slide } from "@material-ui/core";
|
||||
|
||||
function getScrollY(scroller) {
|
||||
return scroller.pageYOffset !== undefined
|
||||
? scroller.pageYOffset
|
||||
: scroller.scrollTop !== undefined
|
||||
? scroller.scrollTop
|
||||
: (document.documentElement || document.body.parentNode || document.body)
|
||||
.scrollTop;
|
||||
}
|
||||
|
||||
const useHideOnScroll = options => {
|
||||
const { threshold, scroller } = options;
|
||||
|
||||
const scrollRef = React.useRef();
|
||||
const [hide, setHide] = React.useState(false);
|
||||
|
||||
const handleScroll = React.useCallback(() => {
|
||||
const scrollY = getScrollY(scroller || window);
|
||||
const prevScrollY = scrollRef.current;
|
||||
scrollRef.current = scrollY;
|
||||
|
||||
setHide(
|
||||
scrollY < prevScrollY
|
||||
? false
|
||||
: scrollY > prevScrollY &&
|
||||
scrollY > (threshold != null ? threshold : 100)
|
||||
? true
|
||||
: false
|
||||
);
|
||||
}, [scroller, threshold]);
|
||||
|
||||
React.useEffect(() => {
|
||||
window.addEventListener("scroll", handleScroll);
|
||||
return () => {
|
||||
window.removeEventListener("scroll", handleScroll);
|
||||
};
|
||||
}, [handleScroll]);
|
||||
|
||||
return hide;
|
||||
};
|
||||
|
||||
export default function HideOnScroll(props) {
|
||||
const { children, threshold, scroller, ...other } = props;
|
||||
|
||||
const hide = useHideOnScroll({ threshold, scroller });
|
||||
|
||||
return (
|
||||
<Slide appear={false} direction="down" in={!hide} {...other}>
|
||||
{children}
|
||||
</Slide>
|
||||
);
|
||||
}
|
||||
@@ -1,29 +1,50 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import SvgIcon from '@material-ui/core/SvgIcon';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import AssignmentIcon from '@material-ui/icons/Assignment';
|
||||
import RoomIcon from '@material-ui/icons/Room';
|
||||
import LocationOnIcon from '@material-ui/icons/LocationOn';
|
||||
import BusinessCenterIcon from '@material-ui/icons/BusinessCenter';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
const variantIcon = {
|
||||
assignment: AssignmentIcon,
|
||||
room: RoomIcon,
|
||||
location_on: LocationOnIcon,
|
||||
business_center: BusinessCenterIcon,
|
||||
};
|
||||
|
||||
const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
height: '60vh',
|
||||
width: '100%',
|
||||
root: {
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'center',
|
||||
marginLeft: theme.spacing(3),
|
||||
marginRight: theme.spacing(3),
|
||||
},
|
||||
icon: {
|
||||
fontSize: '1.1em',
|
||||
},
|
||||
text: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '200',
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
class IconText extends React.Component {
|
||||
|
||||
render() {
|
||||
|
||||
const { classes, variant, text } = this.props;
|
||||
const Icon = variantIcon[variant];
|
||||
return (
|
||||
<Fragment>
|
||||
<SvgIcon>
|
||||
<path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" />
|
||||
</SvgIcon>
|
||||
</Fragment>
|
||||
<Grid container className={classes.root}>
|
||||
<Grid item xs={1}>
|
||||
<Icon className={classes.icon} />
|
||||
</Grid>
|
||||
<Grid item xs={9}>
|
||||
<Typography className={classes.text}>{text}</Typography>
|
||||
</Grid>
|
||||
</Grid>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -31,6 +52,7 @@ class IconText extends React.Component {
|
||||
IconText.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(IconText);
|
||||
@@ -0,0 +1,54 @@
|
||||
import React from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import AssignmentIcon from '@material-ui/icons/Assignment';
|
||||
import RoomIcon from '@material-ui/icons/Room';
|
||||
import LocationOnIcon from '@material-ui/icons/LocationOn';
|
||||
import BusinessCenterIcon from '@material-ui/icons/BusinessCenter';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
const variantIcon = {
|
||||
assignment: AssignmentIcon,
|
||||
room: RoomIcon,
|
||||
location_on: LocationOnIcon,
|
||||
business_center: BusinessCenterIcon,
|
||||
};
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
icon: {
|
||||
fontSize: '1.1em',
|
||||
},
|
||||
text: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '200',
|
||||
},
|
||||
});
|
||||
|
||||
class IconTextInline extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, variant, text } = this.props;
|
||||
const Icon = variantIcon[variant];
|
||||
return (
|
||||
<section className={classes.root}>
|
||||
<Icon className={classes.icon} />
|
||||
<Typography className={classes.text}>{text}</Typography>
|
||||
</section>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
IconTextInline.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(IconTextInline);
|
||||
@@ -0,0 +1,113 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import { Snackbar } from '@material-ui/core';
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
|
||||
const styles = theme => ({
|
||||
close: {
|
||||
padding: theme.spacing.unit / 2,
|
||||
},
|
||||
});
|
||||
var EdeclicLib = null
|
||||
class Messager extends Component {
|
||||
state = {
|
||||
openSnack: false,
|
||||
title: '',
|
||||
body: '',
|
||||
link: '',
|
||||
tokenStopListener: null,
|
||||
messageStopListener: null,
|
||||
}
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
EdeclicLib = (await import('./firebase-client.js')).default
|
||||
if (!EdeclicLib.messaging)
|
||||
return
|
||||
this.setState({ tokenStopListener: EdeclicLib.messaging.onTokenRefresh(this.RefreshToken) })
|
||||
this.setState({ messageStopListener: EdeclicLib.messaging.onMessage(this.MessageReceiver) })
|
||||
|
||||
}
|
||||
componentWillUnmount() {
|
||||
|
||||
if (process.browser) {
|
||||
if (!EdeclicLib.messaging)
|
||||
return
|
||||
if (!!this.state.tokenStopListener)
|
||||
this.state.tokenStopListener();
|
||||
if (!!this.state.messageStopListener)
|
||||
this.state.messageStopListener();
|
||||
}
|
||||
}
|
||||
|
||||
RefreshToken = () => {
|
||||
EdeclicLib.messaging.getToken().then(function (refreshedToken) {
|
||||
console.info('Token refreshed.');
|
||||
// Indicate that the new Instance ID token has not yet been sent to the
|
||||
// app server.
|
||||
EdeclicLib.setTokenSentToServer(false);
|
||||
// Send Instance ID token to app server.
|
||||
EdeclicLib.sendTokenToServer(refreshedToken);
|
||||
|
||||
}).catch(function (err) {
|
||||
console.warning('Unable to retrieve refreshed token ', err);
|
||||
|
||||
});
|
||||
}
|
||||
// Handle incoming messages. Called when:
|
||||
// - a message is received while the app has focus
|
||||
// - the user clicks on an app notification created by a service worker
|
||||
// `messaging.setBackgroundMessageHandler` handler.
|
||||
MessageReceiver = (payload) => {
|
||||
console.info('Message received in Messager. ', payload);
|
||||
|
||||
this.setState({
|
||||
openSnack: true,
|
||||
title: payload.notification.title,
|
||||
body: payload.notification.body,
|
||||
link: payload.notification.click_action
|
||||
});
|
||||
|
||||
}
|
||||
handleClose = () => {
|
||||
this.setState({ openSnack: false });
|
||||
|
||||
}
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'top',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
open={this.state.openSnack}
|
||||
autoHideDuration={6000}
|
||||
onClose={this.handleClose}
|
||||
ContentProps={{
|
||||
'aria-describedby': 'message-id',
|
||||
}}
|
||||
message={<span id="message-id">{this.state.title + " : " + this.state.body}</span>}
|
||||
action={[
|
||||
<a href={this.state.link}>Voir</a>,
|
||||
<IconButton
|
||||
key="close"
|
||||
aria-label="Close"
|
||||
color="inherit"
|
||||
className={classes.close}
|
||||
onClick={this.handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>,
|
||||
]}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default withStyles(styles)(Messager);
|
||||
@@ -0,0 +1,36 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
padding: theme.spacing(0, 4),
|
||||
},
|
||||
text: {
|
||||
fontFamily: 'Dosis',
|
||||
fontSize: '1,2em',
|
||||
fontWeight: '500',
|
||||
color: theme.palette.primary.main,
|
||||
textAlign: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
function NoResult(props) {
|
||||
const { classes } = props;
|
||||
|
||||
return (
|
||||
<div className={ classes.container }>
|
||||
<Typography className={ classes.text }>
|
||||
{props.text}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
NoResult.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
text: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(NoResult)
|
||||
@@ -0,0 +1,33 @@
|
||||
import React from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Head from 'next/head'
|
||||
|
||||
const OgHead = props => {
|
||||
return (
|
||||
<Head>
|
||||
<title key="title">{props.seo_title}</title>
|
||||
<meta name="description" key="description" content={props.seo_description} />
|
||||
|
||||
<meta key="og:url" property="og:url" content={props.seo_url} />
|
||||
<meta key="og:type" property="og:type" content="article" />
|
||||
<meta key="og:title" property="og:title" content={props.seo_title} />
|
||||
<meta
|
||||
key="og:description"
|
||||
property="og:description"
|
||||
content={props.seo_description}
|
||||
/>
|
||||
<meta key="og:image" property="og:image" content={props.image} />
|
||||
|
||||
<link rel="canonical" href={"https://www.application.bzh/" + props.seo_url} />
|
||||
</Head>
|
||||
)
|
||||
}
|
||||
|
||||
OgHead.propTypes = {
|
||||
// seo_title: PropTypes.string.isRequired,
|
||||
// seo_description: PropTypes.string.isRequired,
|
||||
// seo_url: PropTypes.string.isRequired,
|
||||
// image: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default OgHead
|
||||
@@ -0,0 +1,51 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
start: {
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
center: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
end: {
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
});
|
||||
|
||||
class RootContainer extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, children, align } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{(() => {
|
||||
switch(align) {
|
||||
case 'start':
|
||||
return <section className={classes.root + ' ' + classes.start}>{children}</section>;
|
||||
case 'center':
|
||||
return <section className={classes.root + ' ' + classes.center}>{children}</section>;
|
||||
case 'end':
|
||||
return <section className={classes.root + ' ' + classes.end}>{children}</section>;
|
||||
default:
|
||||
return <section className={classes.root}>{children}</section>;
|
||||
}
|
||||
})()}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
RootContainer.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
}
|
||||
|
||||
export default withStyles(styles)(RootContainer);
|
||||
@@ -0,0 +1,131 @@
|
||||
import React from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import clsx from 'clsx';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
import CardMedia from '@material-ui/core/CardMedia';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import Collapse from '@material-ui/core/Collapse';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { red } from '@material-ui/core/colors';
|
||||
import FavoriteIcon from '@material-ui/icons/Favorite';
|
||||
import ShareIcon from '@material-ui/icons/Share';
|
||||
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
|
||||
import MoreVertIcon from '@material-ui/icons/MoreVert';
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
card: {
|
||||
width: '100%',
|
||||
},
|
||||
media: {
|
||||
height: 0,
|
||||
paddingTop: '56.25%', // 16:9
|
||||
},
|
||||
expand: {
|
||||
transform: 'rotate(0deg)',
|
||||
transition: theme.transitions.create('transform', {
|
||||
duration: theme.transitions.duration.shortest,
|
||||
}),
|
||||
width: '0.7em',
|
||||
height: '0.7em',
|
||||
fill: theme.palette.primary.dark,
|
||||
},
|
||||
expandOpen: {
|
||||
transform: 'rotate(180deg)',
|
||||
width: '0.7em',
|
||||
height: '0.7em',
|
||||
fill: theme.palette.primary.dark,
|
||||
},
|
||||
rootCard: {
|
||||
boxShadow: 'none !important',
|
||||
},
|
||||
rootCardAction: {
|
||||
margin: theme.spacing(1,0),
|
||||
'& button': {
|
||||
padding: theme.spacing(2,0),
|
||||
borderRadius: 0,
|
||||
}
|
||||
},
|
||||
rootCardContent: {
|
||||
padding: 0 + '!important',
|
||||
},
|
||||
containerCollapse: {
|
||||
position: 'relative',
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
content: '""',
|
||||
background: 'linear-gradient(to top,rgba(255, 255, 255, 1) 8%, rgba(255, 255, 255, 0) 50%)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
containerCollapseDefault: {
|
||||
position: 'relative',
|
||||
'&:before': {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
content: '""',
|
||||
background: ' rgba(255, 255, 255, 0)',
|
||||
pointerEvents: 'none',
|
||||
},
|
||||
},
|
||||
displayMore: {
|
||||
color: theme.palette.primary.dark,
|
||||
fontWeight: '600',
|
||||
fontSize: '0.8rem',
|
||||
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}));
|
||||
|
||||
export default function SimpleCollapse(props) {
|
||||
const classes = useStyles();
|
||||
const [expanded, setExpanded] = React.useState(false);
|
||||
|
||||
function handleExpandClick() {
|
||||
setExpanded(!expanded);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={classes.card} classes={{ root: classes.rootCard }}>
|
||||
<Collapse in={expanded} timeout="auto" collapsedHeight={isWidthUp('sm', props.width) ? '28vh' : '30vh'} className={clsx(classes.containerCollapse, {
|
||||
[classes.containerCollapseDefault]: expanded,
|
||||
})}>
|
||||
<CardContent classes={{ root: classes.rootCardContent }}>
|
||||
{props.children}
|
||||
</CardContent>
|
||||
</Collapse>
|
||||
|
||||
<CardActions disableSpacing classes={{ root: classes.rootCardAction }}>
|
||||
|
||||
|
||||
<IconButton
|
||||
onClick={handleExpandClick}
|
||||
aria-expanded={expanded}
|
||||
aria-label="Afficher plus"
|
||||
> <Typography className={classes.displayMore}>
|
||||
{(!expanded) ? 'Afficher plus' : 'Afficher moins'}
|
||||
</Typography>
|
||||
<ExpandMoreIcon className={clsx(classes.expand, {
|
||||
[classes.expandOpen]: expanded,
|
||||
})} />
|
||||
</IconButton>
|
||||
|
||||
</CardActions>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import Link from 'next/link';
|
||||
import clsx from 'clsx';
|
||||
import { loadCSS } from 'fg-loadcss';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { red } from '@material-ui/core/colors';
|
||||
import Icon from '@material-ui/core/Icon';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import ButtonGroup from '@material-ui/core/ButtonGroup';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
icon: {
|
||||
fontSize: '1em',
|
||||
},
|
||||
externalButton: {
|
||||
'& .MuiButtonGroup-groupedContained': {
|
||||
border: '0',
|
||||
},
|
||||
margin: theme.spacing(1,0),
|
||||
margin: theme.spacing(1,1.2),
|
||||
width: 'auto',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
minWidth: '20vw',
|
||||
width: '50%',
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
width: '45%',
|
||||
},
|
||||
},
|
||||
linkedinButton: {
|
||||
backgroundColor: theme.palette.authSocial.linkedin,
|
||||
padding: theme.spacing(1),
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.authSocial.linkedin,
|
||||
}
|
||||
},
|
||||
facebookButton: {
|
||||
backgroundColor: theme.palette.authSocial.facebook,
|
||||
padding: theme.spacing(1),
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.authSocial.facebook,
|
||||
}
|
||||
},
|
||||
linkedinIconButton: {
|
||||
backgroundColor: theme.palette.authSocial.linkedinDark,
|
||||
color: theme.palette.authSocial.linkedinDark,
|
||||
border: 0,
|
||||
},
|
||||
facebookIconButton: {
|
||||
backgroundColor: theme.palette.authSocial.facebookDark,
|
||||
color: theme.palette.authSocial.facebookDark,
|
||||
border: 0,
|
||||
},
|
||||
textButton: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%',
|
||||
fontWeight: '400',
|
||||
color: theme.palette.secondary.main,
|
||||
textTransform: 'inherit',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
minWidth: '15vw',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default function SocialButtonsAuth(props) {
|
||||
const classes = useStyles();
|
||||
|
||||
React.useEffect(() => {
|
||||
loadCSS(
|
||||
'https://use.fontawesome.com/releases/v5.1.0/css/all.css',
|
||||
document.querySelector('#font-awesome-css'),
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<ButtonGroup variant="contained" onClick={props.linkedInAuth} className={classes.externalButton}>
|
||||
<Button className={classes.linkedinIconButton}>
|
||||
<Icon className={clsx(classes.icon, 'fab fa-linkedin-in')} color="secondary" />
|
||||
</Button>
|
||||
|
||||
<Button className={classes.textButton +' '+ classes.linkedinButton+ ' cypLinkedInConnexion'} >
|
||||
Connexion avec Linkedin
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
|
||||
<ButtonGroup variant="contained" onClick={props.facebookAuth} className={classes.externalButton}>
|
||||
<Button className={classes.facebookIconButton}>
|
||||
<Icon className={clsx(classes.icon, 'fab fa-facebook-f')} color="secondary" />
|
||||
</Button>
|
||||
|
||||
<Button className={classes.textButton +' '+ classes.facebookButton} >
|
||||
Connexion avec Facebook
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
'use strict';
|
||||
|
||||
export const payloadFromSubscription = function(subscription) {
|
||||
var key = subscription.getKey ? subscription.getKey('p256dh') : '';
|
||||
var auth = subscription.getKey ? subscription.getKey('auth') : '';
|
||||
// NOTE: p256dg and auth are encoded into std base64, NOT urlsafe base64
|
||||
return {
|
||||
endpoint: subscription.endpoint,
|
||||
keys: {
|
||||
p256dh: key ? btoa(String.fromCharCode.apply(null, new Uint8Array(key))) : '',
|
||||
auth: auth ? btoa(String.fromCharCode.apply(null, new Uint8Array(auth))) : ''
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export const urlB64ToUint8Array= function(base64String) {
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4)
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/\-/g, '+')
|
||||
.replace(/_/g, '/')
|
||||
|
||||
const rawData = window.atob(base64)
|
||||
const outputArray = new Uint8Array(rawData.length)
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i)
|
||||
}
|
||||
return outputArray
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
function redirect(params) {
|
||||
const { Router, ctx, location, status = 302 } = params
|
||||
|
||||
if (ctx.res) {
|
||||
// Seems to be the version used by zeit
|
||||
ctx.res.writeHead(status, {
|
||||
Location: location,
|
||||
// Add the content-type for SEO considerations
|
||||
'Content-Type': 'text/html; charset=utf-8',
|
||||
})
|
||||
ctx.res.end()
|
||||
return
|
||||
}
|
||||
|
||||
Router.replace(location)
|
||||
}
|
||||
|
||||
export default redirect
|
||||
@@ -0,0 +1,55 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import _ from "lodash"
|
||||
import Ar from "lodash/array"
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
video: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
paddingBottom: '56%', /* 16:9 58%*/
|
||||
'& iframe': {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 'calc(50% - 50vw)',
|
||||
width: '100vw',
|
||||
height: '100%',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
paddingBottom: '36%', /* 16:9 */
|
||||
'& iframe': {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 'calc(50% - 32vw)',
|
||||
width: '64vw',
|
||||
height: '100%',
|
||||
},
|
||||
},
|
||||
}
|
||||
}));
|
||||
|
||||
export default function VideoVimeo(props) {
|
||||
|
||||
const {videoId, title, options, classesOverride, autoplay} = props;
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<section className={classes.video +' '+classesOverride}>
|
||||
<iframe className='player'
|
||||
src={"https://player.vimeo.com/video/"+ videoId + options+"?autoplay="+autoplay}
|
||||
frameBorder="0"
|
||||
title={title}
|
||||
webkitallowfullscreen="true" mozallowfullscreen="true" allowFullScreen></iframe>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
VideoVimeo.defaultProps = {
|
||||
options: "",
|
||||
};
|
||||
@@ -0,0 +1,176 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import { connect } from "react-redux";
|
||||
import { subscribeUserToPushRequest } from '../../actions/user'
|
||||
|
||||
import { urlB64ToUint8Array, payloadFromSubscription } from './Utility'
|
||||
|
||||
var swRegistration = null
|
||||
|
||||
//TODO: put in config
|
||||
const applicationServerPublicKey = 'BNFyiyPiLH97Dgc0m1vXluUNaCN6y3Hkgd4YcqS2lVkP8my3tq2D5gM5g7cY6R4VxC7GljSzkLRu+k+oO+2TtjU=';
|
||||
|
||||
const WebPushContext = React.createContext()
|
||||
|
||||
function WebPushConsumer(props) {
|
||||
return (
|
||||
<WebPushContext.Consumer {...props}>
|
||||
{context => {
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
`Webpush.Consumer cannot be rendered outside the Webpush component`,
|
||||
)
|
||||
}
|
||||
return props.children(context)
|
||||
}}
|
||||
</WebPushContext.Consumer>
|
||||
)
|
||||
}
|
||||
class WebPush extends Component {
|
||||
static Consumer = WebPushConsumer;
|
||||
|
||||
state = {
|
||||
on: false,
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (swRegistration === null) {
|
||||
this.onRegisterServiceWorker()
|
||||
}
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
console.log("isUserSubscribedToPush ::: " + nextProps.isUserSubscribedToPush);
|
||||
if (this.props.subscriveUserEnabled != nextProps.subscriveUserEnabled && nextProps.subscriveUserEnabled) {
|
||||
this.onSubscribeUser()
|
||||
}
|
||||
if (!!nextProps.isUserSubscribedToPush) {
|
||||
this.setState({ on: true })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onUpdateSubscriptionOnServer = (subscription) => {
|
||||
console.log("onUpdateSubscriptionOnServer:", subscription)
|
||||
var payload = payloadFromSubscription(subscription)
|
||||
console.log("payload:", JSON.stringify(payload))
|
||||
this.setState({ subscription: subscription })
|
||||
|
||||
this.props.subscribeUserToPushRequest(payload);
|
||||
|
||||
this.setState(({ on }) => ({ on: !on }))
|
||||
}
|
||||
|
||||
onSubscriptionFailed = (error) => {
|
||||
console.log("onSubscriptionFailed:", error)
|
||||
this.setState(({ on }) => ({ on: false }))
|
||||
}
|
||||
|
||||
toggleSubscription = () => {
|
||||
console.log(this.state.on)
|
||||
if (!this.state.on) {
|
||||
this.onSubscribeUser()
|
||||
}
|
||||
else {
|
||||
this.onUnSubscribeUser()
|
||||
}
|
||||
}
|
||||
|
||||
onSubscribeUser = () => {
|
||||
if (swRegistration == null) {
|
||||
return
|
||||
}
|
||||
|
||||
swRegistration.pushManager.getSubscription()
|
||||
.then((subscription) => {
|
||||
var isSubscribed = !(subscription === null)
|
||||
if (isSubscribed) {
|
||||
this.onUpdateSubscriptionOnServer(subscription)
|
||||
} else {
|
||||
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey)
|
||||
swRegistration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: applicationServerKey
|
||||
})
|
||||
.then((subscription) => {
|
||||
console.log('User is subscribed.')
|
||||
if (this.onUpdateSubscriptionOnServer) {
|
||||
this.onUpdateSubscriptionOnServer(subscription)
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.log('Failed to subscribe the user: ', err)
|
||||
if (onSubscribeFailed) {
|
||||
this.onSubscriptionFailed(err)
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onUnSubscribeUser = () => {
|
||||
swRegistration.pushManager.getSubscription()
|
||||
|
||||
.then((subscription) => {
|
||||
if (subscription) {
|
||||
|
||||
return subscription.unsubscribe();
|
||||
}
|
||||
})
|
||||
.then(() =>
|
||||
this.setState(({ on }) => ({ on: false })))
|
||||
//TODO: facultatif update DB
|
||||
// .then(() => {
|
||||
// return fetch(//, {
|
||||
// method: 'POST',
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// },
|
||||
// body: JSON.stringify(subscription)
|
||||
// })
|
||||
// .then((response) => {
|
||||
// if (!response.ok) {
|
||||
// throw new Error('Bad status code from server.');
|
||||
// }
|
||||
// ///TODO bind UI
|
||||
// return true;
|
||||
// })
|
||||
|
||||
// })
|
||||
.catch((err) => {
|
||||
console.error('Failed to unsubscribe the user.', err);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
onRegisterServiceWorker = () => {
|
||||
navigator.serviceWorker.register('/static/js/Edeclic-service-worker.js')
|
||||
.then(function (swReg) {
|
||||
console.info('Service Worker is registered', swReg);
|
||||
|
||||
swRegistration = swReg;
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (<WebPushContext.Provider
|
||||
value={{ on: this.state.on, toggleSubscription: this.toggleSubscription }}
|
||||
{...this.props}
|
||||
/>)
|
||||
}
|
||||
}
|
||||
|
||||
WebPush.propTypes = {
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
isUserSubscribedToPush: state.user.isUserSubscribedToPush
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, { subscribeUserToPushRequest })(WebPush);
|
||||
@@ -0,0 +1,138 @@
|
||||
//var firebase = require("firebase");
|
||||
import firebase from "firebase/app"
|
||||
import 'firebase/database'
|
||||
import 'firebase/messaging'
|
||||
//based on
|
||||
//https://stackoverflow.com/questions/1479319/simplest-cleanest-way-to-implement-singleton-in-javascript
|
||||
class Singleton {
|
||||
// Properties & Methods
|
||||
messaging;
|
||||
fcmToken;
|
||||
|
||||
constructor() {
|
||||
console.log('firebase-client constructor')
|
||||
if (!Singleton.instance) {
|
||||
Singleton.instance = this
|
||||
}
|
||||
// Initialize object
|
||||
if (!firebase.messaging.isSupported()) {
|
||||
console.log('firebase.messaging not supported')
|
||||
return;
|
||||
}
|
||||
|
||||
let config = {
|
||||
apiKey: "AIzaSyArZab_4hUDgksQ4Dqdwv970JyRSlbGaKY",
|
||||
authDomain: "edeclicpwa.firebaseapp.com",
|
||||
databaseURL: "https://edeclicpwa.firebaseio.com",
|
||||
projectId: "edeclicpwa",
|
||||
storageBucket: "edeclicpwa.appspot.com",
|
||||
messagingSenderId: "130215947970"
|
||||
}
|
||||
firebase.initializeApp(config);
|
||||
|
||||
navigator.serviceWorker
|
||||
.register('/service-worker.js')
|
||||
.then((registration) => {
|
||||
firebase.messaging().useServiceWorker(registration);
|
||||
});
|
||||
this.messaging = firebase.messaging();
|
||||
this.messaging.usePublicVapidKey('BEiMSVYvblP5oKcIY7b90r-5xLk1QYDra1sSdZHdaynihcb3Hx9wdZdI9IUHmbqureB_-IpOFKpm7arBB93J3rk');
|
||||
console.log('FireBase Singleton Initialized')
|
||||
return Singleton.instance
|
||||
}
|
||||
|
||||
isSupported() {
|
||||
this.messaging.app.firebase_.messaging.isSupported()
|
||||
}
|
||||
|
||||
async requestPermission() {
|
||||
console.log('Requesting permission...');
|
||||
|
||||
return this.messaging.requestPermission().then(function () {
|
||||
console.log('Notification permission granted.');
|
||||
return true
|
||||
}).catch(function (err) {
|
||||
console.log('Unable to get permission to notify.', err);
|
||||
return false
|
||||
});
|
||||
}
|
||||
|
||||
async askForToken() {
|
||||
var res = await this.messaging.getToken().then(async (currentToken) => {
|
||||
if (currentToken) {
|
||||
console.log(currentToken)
|
||||
// this.fcmToken = currentToken;
|
||||
await this.sendTokenToServer(currentToken);
|
||||
return currentToken;
|
||||
}
|
||||
else {
|
||||
// Show permission request.
|
||||
console.log('No Instance ID token available. Request permission to generate one.');
|
||||
let hasPerm = await this.requestPermission()
|
||||
if (hasPerm)
|
||||
{
|
||||
debugger;
|
||||
return await this.askForToken()
|
||||
}
|
||||
else
|
||||
await this.setTokenSentToServer(false);
|
||||
}
|
||||
}).catch(async (err) => {
|
||||
console.log('An error occurred while retrieving token. ', err);
|
||||
// showToken('Error retrieving Instance ID token. ', err);
|
||||
await this.setTokenSentToServer(false);
|
||||
})
|
||||
console.log("Token : ")
|
||||
console.log(res);
|
||||
return res
|
||||
}
|
||||
|
||||
async deleteToken() {
|
||||
// Delete Instance ID token.
|
||||
// [START delete_token]
|
||||
this.messaging.getToken().then(async (currentToken) => {
|
||||
this.messaging.deleteToken(currentToken).then(async () => {
|
||||
console.log('Token deleted.');
|
||||
await this.setTokenSentToServer(false);
|
||||
// [START_EXCLUDE]
|
||||
// Once token is deleted update UI.
|
||||
//resetUI();
|
||||
// [END_EXCLUDE]
|
||||
}).catch((err) => {
|
||||
console.log('Unable to delete token. ', err);
|
||||
});
|
||||
// [END delete_token]
|
||||
}).catch((err) => {
|
||||
console.log('Error retrieving Instance ID token. ', err);
|
||||
//showToken('Error retrieving Instance ID token. ', err);
|
||||
});
|
||||
}
|
||||
|
||||
async sendTokenToServer(currentToken) {
|
||||
var tokenSent = await this.isTokenSentToServer()
|
||||
if (!tokenSent) {
|
||||
console.log('Sending token to server...');
|
||||
firebase.database().ref('subscription/' + currentToken).set({
|
||||
general: true
|
||||
});
|
||||
|
||||
this.setTokenSentToServer(true);
|
||||
} else {
|
||||
console.log('Token already sent to server so won\'t send it again ' +
|
||||
'unless it changes');
|
||||
}
|
||||
}
|
||||
|
||||
async isTokenSentToServer() {
|
||||
return window.localStorage.getItem('sentToServer') === '1';
|
||||
}
|
||||
|
||||
async setTokenSentToServer(sent) {
|
||||
window.localStorage.setItem('sentToServer', sent ? '1' : '0');
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new Singleton()
|
||||
Object.freeze(instance)
|
||||
|
||||
export default instance
|
||||
@@ -0,0 +1,106 @@
|
||||
|
||||
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import SnackbarContent from '@material-ui/core/SnackbarContent';
|
||||
import React, { Component } from 'react';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import ErrorIcon from '@material-ui/icons/Error';
|
||||
import InfoIcon from '@material-ui/icons/Info';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import CheckCircleIcon from '@material-ui/icons/CheckCircle';
|
||||
import WarningIcon from '@material-ui/icons/Warning';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import clsx from 'clsx';
|
||||
import { amber, green } from '@material-ui/core/colors';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
const useStyles1 = makeStyles(theme => ({
|
||||
success: {
|
||||
backgroundColor: green[600],
|
||||
},
|
||||
error: {
|
||||
backgroundColor: theme.palette.error.dark,
|
||||
},
|
||||
info: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
},
|
||||
warning: {
|
||||
backgroundColor: amber[700],
|
||||
},
|
||||
icon: {
|
||||
fontSize: 20,
|
||||
},
|
||||
iconVariant: {
|
||||
opacity: 0.9,
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
message: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
},
|
||||
}));
|
||||
const variantIcon = {
|
||||
success: CheckCircleIcon,
|
||||
warning: WarningIcon,
|
||||
error: ErrorIcon,
|
||||
info: InfoIcon,
|
||||
};
|
||||
|
||||
function MySnackbarContentWrapper(props) {
|
||||
const classes = useStyles1();
|
||||
const { className, message, onClose, variant, ...other } = props;
|
||||
const Icon = variantIcon[variant];
|
||||
|
||||
return (
|
||||
<SnackbarContent
|
||||
className={clsx(classes[variant], className)}
|
||||
aria-describedby="client-snackbar"
|
||||
message={
|
||||
<span id="client-snackbar" className={classes.message}>
|
||||
<Icon className={clsx(classes.icon, classes.iconVariant)} />
|
||||
{message}
|
||||
</span>
|
||||
}
|
||||
action={[
|
||||
<IconButton key="close" aria-label="Close" color="inherit" onClick={onClose}>
|
||||
<CloseIcon className={classes.icon} />
|
||||
</IconButton>,
|
||||
]}
|
||||
{...other}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
|
||||
|
||||
});
|
||||
|
||||
class Snack extends Component {
|
||||
render() {
|
||||
return (
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'right',
|
||||
}}
|
||||
open={this.props.openSnack}
|
||||
autoHideDuration={7000}
|
||||
>
|
||||
<MySnackbarContentWrapper
|
||||
variant={this.props.variant}
|
||||
message={this.props.message}
|
||||
onClose={this.props.onClose} />
|
||||
</Snackbar>
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Snack);
|
||||
|
||||
@@ -3,29 +3,184 @@ import red from '@material-ui/core/colors/red';
|
||||
|
||||
// Create a theme instance.
|
||||
const theme = createMuiTheme({
|
||||
|
||||
// COLORS
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#155EE4',
|
||||
main: '#1689FB',
|
||||
darkMain: '#155EE4',
|
||||
dark: '#1E2327',
|
||||
important: '#25D89A',
|
||||
conditional: '#D2D3D3',
|
||||
},
|
||||
secondary: {
|
||||
main: '#19857b',
|
||||
},
|
||||
flash: {
|
||||
main: '#25D89A',
|
||||
main: '#FFFFFF',
|
||||
dark: '#BBBCBD',
|
||||
important: '#FF5A5F',
|
||||
text: '#787B7D',
|
||||
},
|
||||
error: {
|
||||
main: red.A400,
|
||||
},
|
||||
background: {
|
||||
default: '#fff',
|
||||
offerlistitem: {
|
||||
title: '#1E2327',
|
||||
icon: '#BBBCBD',
|
||||
text: '#787B7D',
|
||||
},
|
||||
answer: {
|
||||
main: '#E8E8E8',
|
||||
},
|
||||
background: {
|
||||
default: '#ffffff',
|
||||
grid: '#FCFCFC',
|
||||
dark: '#1E2327',
|
||||
grey: '#F3F9FF',
|
||||
videotheque: '#54D7E6',
|
||||
blog: '#1689FB',
|
||||
recruteur: '#1E2327',
|
||||
},
|
||||
menu: {
|
||||
darkText: '#1E2327',
|
||||
lightText: '#F3F9FF',
|
||||
darkDivider: '#32383C',
|
||||
lightDivider: '#E2E2E2',
|
||||
backArrow: 'rgba(30, 35, 39, 0.4)',
|
||||
emailText: '#E8E8E9'
|
||||
},
|
||||
tags: {
|
||||
main: '#E7F3FF',
|
||||
},
|
||||
input: {
|
||||
main: '#F0F0F0',
|
||||
},
|
||||
authSocial: {
|
||||
facebook: '#3064B7',
|
||||
facebookDark: '#2C59A0',
|
||||
linkedin: '#0276B5',
|
||||
linkedinDark: '#025C8D',
|
||||
},
|
||||
social: {
|
||||
facebook: '#334F8D',
|
||||
twitter: '#1A97F0',
|
||||
linkedin: '#006CAC',
|
||||
mail: '#BBBCBD',
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
//OVERRIDE
|
||||
overrides: {
|
||||
|
||||
MuiDrawer: {
|
||||
paperAnchorLeft: {
|
||||
right: '25%',
|
||||
backgroundColor: '#1E2327',
|
||||
},
|
||||
paperAnchorRight: {
|
||||
left: '25%',
|
||||
backgroundColor: '#1E2327',
|
||||
}
|
||||
},
|
||||
|
||||
MuiAppBar: {
|
||||
colorPrimary: {
|
||||
color: '#1689FB',
|
||||
backgroundColor: '#FFFFFF',
|
||||
},
|
||||
colorDefault: {
|
||||
boxShadow: 'none',
|
||||
backgroundColor: '#F8FBFF',
|
||||
}
|
||||
},
|
||||
|
||||
MuiPaper: {
|
||||
elevation1: {
|
||||
transition: 'box-shadow .25s',
|
||||
boxShadow: '0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.2)',
|
||||
boxShadow: '0px 6px 16px rgba(30, 35, 39, 0.15)',
|
||||
transition: 'box-shadow 2s',
|
||||
'&:hover': {
|
||||
boxShadow: '0px 6px 16px rgba(35, 40, 45, 0.25)',
|
||||
transition: 'box-shadow 1.2s',
|
||||
}
|
||||
},
|
||||
elevation4: {
|
||||
transition: 'box-shadow .25s',
|
||||
boxShadow: '0px 6px 16px rgba(30, 35, 39, 0.15)',
|
||||
},
|
||||
},
|
||||
|
||||
MuiButton: {
|
||||
root: {
|
||||
'&:hover': {
|
||||
backgroundColor: 'none',
|
||||
},
|
||||
'&:active': {
|
||||
boxShadow: '0px',
|
||||
},
|
||||
},
|
||||
|
||||
contained: {
|
||||
boxShadow: '0px',
|
||||
'&:hover': {
|
||||
backgroundColor: 'none',
|
||||
},
|
||||
'&:active': {
|
||||
boxShadow: '0px',
|
||||
},
|
||||
},
|
||||
outlined: {
|
||||
border: '2px solid',
|
||||
'&:hover': {
|
||||
backgroundColor: 'none',
|
||||
},
|
||||
'&:active': {
|
||||
boxShadow: '0px',
|
||||
},
|
||||
},
|
||||
outlinedPrimary: {
|
||||
border: '2px solid',
|
||||
borderColor: '#1689FB',
|
||||
'&:hover': {
|
||||
border: '2px solid',
|
||||
},
|
||||
},
|
||||
outlinedSecondary: {
|
||||
border: '2px solid',
|
||||
borderColor: '#FFFFFF',
|
||||
'&:hover': {
|
||||
border: '2px solid',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
MuiButtonGroup: {
|
||||
contained: {
|
||||
boxShadow: 'inherit',
|
||||
},
|
||||
},
|
||||
|
||||
MuiLink: {
|
||||
underlineHover: {
|
||||
'&:hover': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
MuiCheckbox: {
|
||||
colorSecondary: {
|
||||
'&.Mui-checked': {
|
||||
color: "#1689FB",
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
MuiFilledInput: {
|
||||
underline: {
|
||||
'&:before, &:after': {
|
||||
display: 'none',
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,328 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { Button, Typography } from '@material-ui/core';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Input from '@material-ui/core/Input';
|
||||
import { connect } from 'react-redux'
|
||||
import { postAdAnswerRequest } from '../../actions/answer'
|
||||
import { updateUserRequest, updateUserPictureRequest } from '../../actions/user'
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import KeyboardArrowRightIcon from '@material-ui/icons/KeyboardArrowRight';
|
||||
import Loader from './../templates/loader'
|
||||
import getConfig from 'next/config'
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
width: '100%',
|
||||
},
|
||||
form: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '24px',
|
||||
paddingBottom: theme.spacing(8),
|
||||
marginBottom: theme.spacing(8),
|
||||
'&>div': {
|
||||
marginTop: theme.spacing(1.5),
|
||||
marginBottom: theme.spacing(1.5),
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: '0 calc(25vw + 24px)',
|
||||
},
|
||||
|
||||
},
|
||||
importContainer: {
|
||||
alignSelf: 'flex-start',
|
||||
},
|
||||
textField: {
|
||||
width: '100%',
|
||||
'&:disabled': {
|
||||
color: theme.palette.secondary.dark,
|
||||
},
|
||||
'& label': {
|
||||
color: theme.palette.primary.dark,
|
||||
},
|
||||
'& fieldset:hover': {
|
||||
borderColor: theme.palette.primary.main + ' !important',
|
||||
},
|
||||
},
|
||||
foot: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
zIndex: '10',
|
||||
width: '100%',
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
padding: theme.spacing(1.2, 0),
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
boxShadow: '0px 0px 10px rgba(30, 35, 39, 0.10)',
|
||||
},
|
||||
rootButton: {
|
||||
textTransform: 'inherit',
|
||||
border: '1px solid',
|
||||
'&:hover': {
|
||||
border: '1px solid',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
textTransform: 'inherit',
|
||||
marginLeft: theme.spacing(8),
|
||||
marginRight: theme.spacing(8),
|
||||
padding: theme.spacing(1.2, 2),
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.primary.important,
|
||||
},
|
||||
|
||||
importButton: {
|
||||
color: theme.palette.primary.main,
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
padding: theme.spacing(3, 2),
|
||||
margin: theme.spacing(1),
|
||||
boxShadow: '0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12), 0 3px 5px -1px rgba(0, 0, 0, 0.14)',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
InputKeyboardArrowRightIcon: {
|
||||
fontSize: '2.5em',
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { publicRuntimeConfig } = getConfig()
|
||||
//https://xd.adobe.com/view/ba98331a-9145-4d67-7e27-25a36e519ef4-cca9/screen/a96779d7-bdb3-410a-8f19-e6987acb98ee/Candidature-Step-3
|
||||
class Answer extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
// This binding is necessary to make `this` work in the callback
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
}
|
||||
|
||||
state = {
|
||||
file: null,
|
||||
firstname: '',
|
||||
lastname: '',
|
||||
phone: '',
|
||||
currentJob: '',
|
||||
linkedin: '',
|
||||
twitter: '',
|
||||
website: '',
|
||||
mediaId: null,
|
||||
companyId: null,
|
||||
};
|
||||
|
||||
handleChange = (e) => {
|
||||
this.setState({ [e.target.name]: e.target.value })
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
firstname: nextProps.user.firstname,
|
||||
lastname: nextProps.user.lastname,
|
||||
phone: nextProps.user.phone,
|
||||
currentJob: nextProps.user.currentJob,
|
||||
linkedin: nextProps.user.linkedin,
|
||||
twitter: nextProps.user.twitter,
|
||||
website: nextProps.user.website,
|
||||
mediaId: nextProps.mediaId,
|
||||
companyId: nextProps.companyId,
|
||||
})
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.setState({
|
||||
firstname: this.props.user.firstname,
|
||||
lastname: this.props.user.lastname,
|
||||
phone: this.props.user.phone,
|
||||
currentJob: this.props.user.currentJob,
|
||||
linkedin: this.props.user.linkedin,
|
||||
twitter: this.props.user.twitter,
|
||||
website: this.props.user.website,
|
||||
})
|
||||
}
|
||||
|
||||
onSubmit = (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
const data = new FormData()
|
||||
|
||||
data.append('firstname', this.state.firstname)
|
||||
data.append('lastname', this.state.lastname)
|
||||
data.append('phone', this.state.phone)
|
||||
data.append('currentJob', this.state.currentJob)
|
||||
data.append('linkedin', this.state.linkedin)
|
||||
data.append('twitter', this.state.twitter)
|
||||
data.append('website', this.state.website)
|
||||
|
||||
//TODO:maybe add this two in a single transactionnal saga
|
||||
this.props.updateUserRequest(data)
|
||||
|
||||
this.props.postAdAnswerRequest(
|
||||
{
|
||||
adId: this.props.adId,
|
||||
companyId: this.props.companyId,
|
||||
mediaId: this.props.mediaId
|
||||
})
|
||||
}
|
||||
|
||||
inputFilesystemChange = (e) => {
|
||||
|
||||
console.log("inputFilesystemChange");
|
||||
var file = e.target.files[0]
|
||||
this.setState({ picture: file })
|
||||
|
||||
const data = new FormData()
|
||||
data.append('file', file)
|
||||
this.props.updateUserPictureRequest(data)
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, user } = this.props
|
||||
const logoUrl = user.picture;
|
||||
return (
|
||||
<section className={classes.container}>
|
||||
<form className={classes.form}
|
||||
autoComplete="off">
|
||||
{!!logoUrl && <Avatar className={classes.logo}
|
||||
classes={{ img: classes.MuiAvatarImg }}
|
||||
src={publicRuntimeConfig.symphonyUrl + publicRuntimeConfig.postedContentUrl + logoUrl} alt="candidatLogo" />}
|
||||
|
||||
{!!user.pictureLoading && <Loader />}
|
||||
{!!!user.pictureLoading && !!!logoUrl &&
|
||||
(<Fragment><input
|
||||
type="file" accept="image/*" capture="filesystem"
|
||||
className={classes.input}
|
||||
style={{ display: 'none' }}
|
||||
id="raised-button-file"
|
||||
multiple
|
||||
type="file"
|
||||
onChange={this.inputFilesystemChange}
|
||||
/>
|
||||
<label htmlFor="raised-button-file">
|
||||
<Button component="span" className={classes.importButton} classes={{ root: classes.rootButton }}>
|
||||
<img alt="talents-tube" src='/static/images/default/account-avatar-default.svg'
|
||||
className={classes.iconButton} />
|
||||
<Typography align="left" className={classes.textButton}>
|
||||
J'<span className={classes.underlined}>importe</span> ma photo de profil
|
||||
</Typography>
|
||||
<KeyboardArrowRightIcon className={classes.InputKeyboardArrowRightIcon} />
|
||||
</Button>
|
||||
</label></Fragment>)
|
||||
}
|
||||
<TextField
|
||||
name="lastname"
|
||||
required
|
||||
id="outlined-required"
|
||||
label="Nom"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
value={this.state.lastname}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
name="firstname"
|
||||
required
|
||||
id="outlined-required"
|
||||
label="Prénom"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
value={this.state.firstname}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
name="phone"
|
||||
id="outlined"
|
||||
label="Téléphone"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
value={this.state.phone}
|
||||
onChange={this.handleChange}
|
||||
type="tel"
|
||||
/>
|
||||
|
||||
<TextField
|
||||
disabled
|
||||
id="outlined-required"
|
||||
label="E-mail"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
value={user.email}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
name="currentJob"
|
||||
id="outlined"
|
||||
label="Poste actuel"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
value={this.state.currentJob}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
name="linkedin"
|
||||
id="outlined"
|
||||
label="Linkedin"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
placeholder="http://www.linkedin.com/in/"
|
||||
value={this.state.linkedin}
|
||||
onChange={this.handleChange}
|
||||
|
||||
/>
|
||||
|
||||
<TextField
|
||||
name="twitter"
|
||||
id="outlined"
|
||||
label="Twitter"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
placeholder="http://twitter.com/.."
|
||||
value={this.state.twitter}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
name="website"
|
||||
id="outlined"
|
||||
label="Site internet"
|
||||
className={classes.textField}
|
||||
classes={{ root: classes.rootTextfield }}
|
||||
variant="outlined"
|
||||
placeholder="http://monsite.fr"
|
||||
value={this.state.website}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
</form>
|
||||
<div id="F" className={classes.foot}>
|
||||
<Button variant="contained" className={classes.button + ' cypButtonSendApplication'} onClick={this.onSubmit}>Postuler</Button>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
user: state.user,
|
||||
loading: !!state.user.loading,
|
||||
pictureLoading: !!state.user.pictureLoading,
|
||||
errors: state.user.errors || ownProps.errors,
|
||||
mediaId: state.media.message && state.media.message.mediaId,
|
||||
};
|
||||
}
|
||||
|
||||
export default withStyles(styles)(connect(mapStateToProps, { postAdAnswerRequest, updateUserRequest, updateUserPictureRequest })(Answer))
|
||||
@@ -0,0 +1,263 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import Router from 'next/router'
|
||||
import getConfig from 'next/config'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardMedia from '@material-ui/core/CardMedia';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import IconText from './../atoms/IconText';
|
||||
import KeyboardArrowleftIcon from '@material-ui/icons/PlayArrowRounded';
|
||||
import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
|
||||
import Link from 'next/link';
|
||||
|
||||
const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
minHeight: '21vh',
|
||||
maxHeight: '25vh',
|
||||
width: '100%',
|
||||
margin: theme.spacing(1,0),
|
||||
borderRadius: '5px',
|
||||
cursor: 'pointer',
|
||||
'& > div': {
|
||||
padding: '0 !important',
|
||||
},
|
||||
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '30%',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '40%',
|
||||
},
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width: '100%',
|
||||
},
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
margin: '1vh 0 1vh 0',
|
||||
},
|
||||
},
|
||||
detailsContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
padding: theme.spacing(3, 2),
|
||||
width: '75%',
|
||||
},
|
||||
jobTitle: {
|
||||
fontSize: '1em',
|
||||
fontWeight: 'bold',
|
||||
color: theme.palette.offerlistitem.title,
|
||||
},
|
||||
companyTitle: {
|
||||
fontSize: '0.8em',
|
||||
fontWeight: '300',
|
||||
color: theme.palette.offerlistitem.title,
|
||||
},
|
||||
content: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: '100%',
|
||||
padding: 0,
|
||||
},
|
||||
cover: {
|
||||
position: 'relative',
|
||||
width: '125px',
|
||||
height: '100%',
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
},
|
||||
iconPlay: {
|
||||
fontSize: '2em',
|
||||
color: theme.palette.background.grey,
|
||||
position: 'absolute',
|
||||
top: 'calc(50% - 0.5em)',
|
||||
left: 'calc(50% - 0.5em)',
|
||||
},
|
||||
iconText: {
|
||||
marginLeft: 0,
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
iconTextIcon: {
|
||||
color: theme.palette.offerlistitem.icon,
|
||||
fontSize: '0.8em',
|
||||
},
|
||||
iconTextText: {
|
||||
color: theme.palette.offerlistitem.text,
|
||||
fontSize: '0.6em',
|
||||
paddingLeft: theme.spacing(1),
|
||||
},
|
||||
bottomCard: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
width: '90%',
|
||||
},
|
||||
chip: {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.primary.important,
|
||||
},
|
||||
icon: {
|
||||
color: theme.palette.secondary.dark,
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
const { publicRuntimeConfig } = getConfig()
|
||||
|
||||
//TODO:shouldn't be here
|
||||
//must go in API
|
||||
const status = new Map([
|
||||
|
||||
["DRAFT"
|
||||
, "Brouillon"]
|
||||
,
|
||||
["TO_PUBLISH"
|
||||
, "En cours de validation"]
|
||||
,
|
||||
["VALIDATED"
|
||||
, "Validée"]
|
||||
,
|
||||
["PUBLISHED"
|
||||
, "Publiée"]
|
||||
,
|
||||
["CLOSED"
|
||||
, "Cloturée"]
|
||||
,
|
||||
["EXPIRED"
|
||||
, "Expirée"]
|
||||
,
|
||||
["SENDED"
|
||||
, "Envoyée"]
|
||||
,
|
||||
["DECLINED"
|
||||
, "Refusée"]
|
||||
,
|
||||
["NEW"
|
||||
, "Nouveau"]
|
||||
,
|
||||
["VIEWED"
|
||||
, "Visionnée"]
|
||||
,
|
||||
["NEGATIVE"
|
||||
, "Négatif"]
|
||||
,
|
||||
["POSITIVE"
|
||||
, "Positif"]
|
||||
,
|
||||
["INFOS"
|
||||
, "Demande d’informations"]
|
||||
,
|
||||
["CONTACTED"
|
||||
, "Demande d’informations"]
|
||||
,
|
||||
["CONTACTED.RDV"
|
||||
, "Convocation entretien"]
|
||||
|
||||
])
|
||||
class AnswerRowComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
|
||||
const { classes, rowData, company } = this.props;
|
||||
// company not used ?
|
||||
let companyR = company
|
||||
if (!!!company)
|
||||
companyR = rowData.ad.company;
|
||||
else
|
||||
companyR = company;
|
||||
|
||||
const imgUrl = (rowData.ad && rowData.ad.video && rowData.ad.video.thumbnails[0] && rowData.ad.video.thumbnails[0].sizes[4])
|
||||
? rowData.ad.video.thumbnails[0].sizes[4].link
|
||||
: publicRuntimeConfig.adListDefaultUrl
|
||||
const logoUrl = publicRuntimeConfig.symphonyUrl + publicRuntimeConfig.cachedMediaUrl + companyR.logo
|
||||
|
||||
return (
|
||||
<Link href={`/Candidature/${rowData.id}`} >
|
||||
<Card className={classes.card} >
|
||||
{rowData.ad ? (
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={imgUrl}
|
||||
title={rowData.ad.title}
|
||||
>
|
||||
<KeyboardArrowleftIcon className={classes.iconPlay} />
|
||||
</CardMedia>
|
||||
|
||||
<div className={classes.detailsContainer}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
className={classes.jobTitle}>
|
||||
{rowData.ad.title&&rowData.ad.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
className={classes.companyTitle}>
|
||||
{rowData.ad.company.brand_name&&rowData.ad.company.brand_name}
|
||||
</Typography>
|
||||
<div className={ classes.bottomCard }>
|
||||
<Chip size="small" label={status.get(rowData.status)} className={classes.chip} />
|
||||
<ArrowForwardIcon className={classes.icon} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
):(
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={publicRuntimeConfig.symphonyUrl + publicRuntimeConfig.cachedMediaUrl + rowData.company.logo}
|
||||
title={rowData.company.brand_name}
|
||||
>
|
||||
<KeyboardArrowleftIcon className={classes.iconPlay} />
|
||||
</CardMedia>
|
||||
|
||||
<div className={classes.detailsContainer}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
className={classes.jobTitle}>
|
||||
Candidature spontanée
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
className={classes.companyTitle}>
|
||||
{rowData.company.brand_name&&rowData.company.brand_name}
|
||||
</Typography>
|
||||
<div className={ classes.bottomCard }>
|
||||
<Chip size="small" label={status.get(rowData.status)} className={classes.chip} />
|
||||
<ArrowForwardIcon className={classes.icon} />
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
)}
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AnswerRowComponent.defaultProps = {
|
||||
company: {}
|
||||
}
|
||||
|
||||
AnswerRowComponent.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
rowData: PropTypes.object.isRequired,
|
||||
company: PropTypes.object
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(AnswerRowComponent);
|
||||
@@ -0,0 +1,39 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import _ from "lodash"
|
||||
import Ar from "lodash/array"
|
||||
import VideoVimeo from '../atoms/VideoVimeo'
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
card: {
|
||||
width: '100%',
|
||||
},
|
||||
title: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '500',
|
||||
},
|
||||
titleBorder: {
|
||||
'&:after': {
|
||||
content: '""',
|
||||
width: '15px',
|
||||
borderBottom: 'solid 2px',
|
||||
borderColor: theme.palette.primary.main + '!important',
|
||||
display: 'flex',
|
||||
},
|
||||
},
|
||||
video: {
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingBottom: '35%', /* 16:9 */
|
||||
},
|
||||
}
|
||||
}));
|
||||
|
||||
export default function CandidatureDetail(props) {
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<VideoVimeo classes={{ video: classes.video }} videoId={Ar.last(_.split(props.video.cdn_url, '/'))} />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
import React, { Fragment, useState, useCallback } from 'react';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
import _ from "lodash"
|
||||
import ChatCard from '../atoms/ChatCard'
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import { Button } from '@material-ui/core';
|
||||
import IconAdd from '@material-ui/icons/Add';
|
||||
import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { useSelector } from 'react-redux'
|
||||
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
card: {
|
||||
width: '100%',
|
||||
},
|
||||
wrapper: {
|
||||
minHeight: '100%',
|
||||
},
|
||||
contain: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'center',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: '2vh 18vw',
|
||||
},
|
||||
},
|
||||
push: {
|
||||
height: '50px'
|
||||
},
|
||||
foot: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: '2vh 18vw',
|
||||
},
|
||||
},
|
||||
chat: {
|
||||
|
||||
},
|
||||
filterForm: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: theme.palette.input.main,
|
||||
padding: theme.spacing(1),
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: theme.spacing(3),
|
||||
},
|
||||
},
|
||||
button: {
|
||||
backgroundColor: 'inherit',
|
||||
borderRadius: '0',
|
||||
'& svg': {
|
||||
fontSize: '1.5rem',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
fontSize: '2rem',
|
||||
},
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
color: theme.palette.secondary.main,
|
||||
background: theme.palette.primary.important,
|
||||
padding: theme.spacing(1),
|
||||
borderRadius: '50%',
|
||||
},
|
||||
video: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '11vh',
|
||||
},
|
||||
title: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '500',
|
||||
},
|
||||
titleBorder: {
|
||||
'&:after': {
|
||||
content: '""',
|
||||
width: '15px',
|
||||
borderBottom: 'solid 2px',
|
||||
borderColor: theme.palette.primary.main + '!important',
|
||||
display: 'flex',
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
function Chat(props) {
|
||||
const classes = useStyles();
|
||||
const [text, setText] = useState("");
|
||||
const dispatch = useDispatch()
|
||||
|
||||
const messages = useSelector(state => state.answer.messages);
|
||||
const loadingMessage = useSelector(state => state.answer.loadingMessage);
|
||||
|
||||
const sendMessage = useCallback(
|
||||
(message) => dispatch({ type: 'POST_MESSAGE_ANSWER_REQUEST', payload: { answerId: props.answerId, message: message } }),
|
||||
[dispatch]
|
||||
)
|
||||
|
||||
const postMessage = () => {
|
||||
sendMessage(text);
|
||||
setText('');
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.wrapper}>
|
||||
<div className={classes.contain}>
|
||||
{
|
||||
_.orderBy(messages, ['c_time'], ['asc']).map((message, index) => {
|
||||
return <ChatCard message={message} key={index}></ChatCard>
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div id="F" className={classes.foot}>
|
||||
<div className={classes.filterForm}>
|
||||
<TextField
|
||||
fullWidth
|
||||
multiline
|
||||
rows={isWidthUp('md', props.width) ? 8 : 1}
|
||||
id="standard-search"
|
||||
name="chattext"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
placeholder={"Message"}
|
||||
className={classes.textField}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
/>
|
||||
<Button onClick={()=>postMessage()}
|
||||
disabled={loadingMessage}
|
||||
variant="contained"
|
||||
className={classes.button}>
|
||||
<ArrowUpwardIcon className={classes.icon}/>
|
||||
</Button >
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
export default withWidth()(Chat)
|
||||
@@ -6,10 +6,12 @@ import { withStyles } from '@material-ui/core/styles';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
import CardMedia from '@material-ui/core/CardMedia';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import IconText from './../atoms/IconText';
|
||||
import KeyboardArrowleftIcon from '@material-ui/icons/PlayArrowRounded';
|
||||
import Link from 'next/link';
|
||||
|
||||
const { publicRuntimeConfig } = getConfig()
|
||||
|
||||
@@ -17,40 +19,57 @@ const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
height: '50vh',
|
||||
height: '67vh',
|
||||
width: '100%',
|
||||
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width : '30%',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width : '40%',
|
||||
},
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width : '100%',
|
||||
cursor: 'pointer',
|
||||
transform: 'translateY(-0px)',
|
||||
transition: 'transform 0.25s',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-15px)',
|
||||
transition: 'transform 0.5s, box-shadow 1.2s',
|
||||
'& .cardHover': {
|
||||
backgroundColor: 'rgba(0,0,0,0)',
|
||||
},
|
||||
},
|
||||
|
||||
margin: '1vh 2vh 1vh 2vh',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
margin: '1vh 0 1vh 0',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '30%',
|
||||
height: '53vh',
|
||||
},
|
||||
borderRadius: 0
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '40%',
|
||||
height: '67vh',
|
||||
},
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width: '100%',
|
||||
},
|
||||
|
||||
margin: theme.spacing(1.5),
|
||||
marginBottom: theme.spacing(3),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
margin: theme.spacing(2, 0),
|
||||
},
|
||||
borderRadius: '4px',
|
||||
},
|
||||
details_container: {
|
||||
detailsContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '8vh 0 0 0',
|
||||
width: '100%',
|
||||
paddingTop: theme.spacing(6),
|
||||
},
|
||||
company_title: {
|
||||
companyTitle: {
|
||||
textAlign: 'center',
|
||||
fontSize: '1em',
|
||||
fontWeight: 'normal',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: '500',
|
||||
minHeight: '5vh',
|
||||
padding: theme.spacing(0,1.5),
|
||||
},
|
||||
content: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
flexWrap: 'wrap',
|
||||
width: '100%',
|
||||
padding: 0,
|
||||
@@ -60,102 +79,108 @@ const styles = theme => ({
|
||||
height: '50%',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
position: 'relative',
|
||||
backgroundBlendMode: 'darken',
|
||||
backgroundColor: 'rgba(30,35,39,0.25)',
|
||||
transition: 'background-color 1s',
|
||||
},
|
||||
logo_container: {
|
||||
logoContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 100,
|
||||
height: 100,
|
||||
width: 85,
|
||||
height: 85,
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -66%)',
|
||||
border: 'solid 5px white',
|
||||
backgroundColor: 'white',
|
||||
boxShadow: '0 8px 6px -6px grey',
|
||||
boxShadow: '0px 6px 15px rgba(30, 35, 39, 0.10)',
|
||||
borderRadius: '6px',
|
||||
},
|
||||
iconTextContainer: {
|
||||
width: '100%',
|
||||
},
|
||||
iconText: {
|
||||
marginLeft: 0,
|
||||
padding: theme.spacing(2, 3),
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
iconTextIcon: {
|
||||
color: theme.palette.offerlistitem.icon,
|
||||
fontSize: '1em',
|
||||
},
|
||||
iconTextText: {
|
||||
color: theme.palette.offerlistitem.text,
|
||||
fontSize: '0.8em',
|
||||
textTransform: 'capitalize',
|
||||
paddingLeft: theme.spacing(1),
|
||||
},
|
||||
iconPlay: {
|
||||
fontSize: '3em',
|
||||
color: theme.palette.background.grey,
|
||||
position: 'absolute',
|
||||
top: 'calc(50% - 0.5em)',
|
||||
left: 'calc(50% - 0.5em)',
|
||||
},
|
||||
logo: {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
objectfit: 'contain',
|
||||
borderRadius: 0,
|
||||
objectFit: 'contain',
|
||||
borderRadius: '0px',
|
||||
},
|
||||
chip: {
|
||||
margin: '0 1vh 0 1vh',
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
|
||||
class CompanyRowComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, theme, rowData } = this.props;
|
||||
|
||||
const imgDefaultUrl = "https://www.talentstube.com/img/default_picture_company.svg"
|
||||
const imgDefaultUrl = "/static/images/default/default_picture_company.svg"
|
||||
|
||||
const imgUrl = (rowData.presentation_video && rowData.presentation_video.thumbnails[0] && rowData.presentation_video.thumbnails[0].sizes[4]) ? rowData.presentation_video.thumbnails[0].sizes[4].link : imgDefaultUrl
|
||||
const logoUrl = publicRuntimeConfig.logoRootUrl + rowData.logo
|
||||
const logoUrl = publicRuntimeConfig.symphonyUrl + publicRuntimeConfig.cachedMediaUrl + rowData.logo
|
||||
let location = "non renseigné"
|
||||
if (rowData.addresses && rowData.addresses.billing && rowData.addresses.billing.city && rowData.addresses.billing.postcode)
|
||||
location = (rowData.addresses.billing.city.toLowerCase() + ', ' + rowData.addresses.billing.postcode);
|
||||
|
||||
const offre = (rowData.count_ads == 1) ? rowData.count_ads + ' offre' : rowData.count_ads + ' offres';
|
||||
const nbOffre = (rowData.count_ads > 0) ? offre : 'Candidature spontanée';
|
||||
|
||||
return (
|
||||
<Card onClick={() => Router.push(`/Company?id=${rowData.id}`)}>
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={imgUrl}
|
||||
title={rowData.title}
|
||||
/>
|
||||
<Link href={`/Entreprise/${rowData.id}-${_.kebabCase(rowData.brand_name)}-${_.kebabCase(rowData.addresses.billing.city)}`} >
|
||||
<Card className={classes.card + ' cypCardCompany'}>
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={classes.cover + ' cardHover'}
|
||||
image={imgUrl}
|
||||
title={rowData.brand_name}
|
||||
>
|
||||
<KeyboardArrowleftIcon className={classes.iconPlay} />
|
||||
</CardMedia>
|
||||
<div className={classes.logoContainer}>
|
||||
<Avatar className={classes.logo} src={logoUrl} alt="companylogo" />
|
||||
</div>
|
||||
|
||||
<div className={classes.logo_container}>
|
||||
<Avatar className={classes.logo} src={logoUrl} />
|
||||
</div>
|
||||
|
||||
<div className={classes.details_container}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.company_title}>
|
||||
{rowData.business_name}
|
||||
</Typography>
|
||||
{/* <Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
align="center"
|
||||
className={classes.job_title}>
|
||||
{rowData.title}
|
||||
</Typography> */}
|
||||
</div>
|
||||
<div>
|
||||
{/* <Chip label={rowData.extra.contract_type} className={classes.chip} />
|
||||
<Chip label={rowData.location.gmap_address.address_components[0].long_name} className={classes.chip} /> */}
|
||||
</div>
|
||||
</CardContent>
|
||||
{/* <CardHeader />
|
||||
<CardContent>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={imgUrl}
|
||||
title={rowData.presentation_video&&rowData.presentation_video.title}
|
||||
/>
|
||||
<Typography gutterBottom component="h3"
|
||||
className={classes.details}>
|
||||
{rowData.brand_name}
|
||||
</Typography>
|
||||
<Typography gutterBottom component="h4"
|
||||
className={classes.details}>
|
||||
{rowData.website}
|
||||
</Typography>
|
||||
<Typography gutterBottom component="h5"
|
||||
className={classes.details}>
|
||||
{rowData.description}
|
||||
</Typography>
|
||||
</CardContent> */}
|
||||
</Card>
|
||||
<div className={classes.detailsContainer}>
|
||||
<Typography
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
className={classes.companyTitle}>
|
||||
{rowData.brand_name}
|
||||
</Typography>
|
||||
<div className={classes.iconTextContainer}>
|
||||
<IconText classes={{ root: classes.iconText, icon: classes.iconTextIcon, text: classes.iconTextText }} variant="location_on" text={location} />
|
||||
</div>
|
||||
</div>
|
||||
<Chip color="primary" label={nbOffre} className={classes.chip} />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,277 @@
|
||||
import Link from 'next/link'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Fragment, Children } from 'react';
|
||||
import { Button } from '@material-ui/core';
|
||||
import { Typography } from '@material-ui/core';
|
||||
import { Fragment } from 'react';
|
||||
import NewsLetterRegistration from './NewsLetterRegistration'
|
||||
import dynamic from 'next/dynamic'
|
||||
import Button from '@material-ui/core/Button'
|
||||
|
||||
const PWAInstallSnack = dynamic(() => import('./PWAInstallSnack'), {
|
||||
ssr: false
|
||||
});
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
height: '10vh'
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
[theme.breakpoints.up('md')]: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-around',
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
padding: theme.spacing(10, 18),
|
||||
},
|
||||
'& a': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
padding: theme.spacing(0, 5),
|
||||
paddingBottom: theme.spacing(3.5),
|
||||
'&:first-child': {
|
||||
paddingTop: theme.spacing(4),
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: 0,
|
||||
'&:first-child': {
|
||||
paddingTop: 0,
|
||||
},
|
||||
},
|
||||
|
||||
},
|
||||
containerRegion: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
padding: theme.spacing(3),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: theme.spacing(8, 0),
|
||||
},
|
||||
|
||||
},
|
||||
titleContainer: {
|
||||
color: theme.palette.primary.darkMain,
|
||||
fontSize: '1.5em',
|
||||
fontFamily: 'Dosis',
|
||||
fontWeight: '600',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
fontSize: '1.2em',
|
||||
paddingBottom: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
textContainer: {
|
||||
fontWeight: '300',
|
||||
fontSize: '0.9em',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
fontWeight: '300',
|
||||
fontSize: '0.8em',
|
||||
},
|
||||
},
|
||||
textContainerRegion: {
|
||||
fontWeight: '300',
|
||||
fontSize: '0.9em',
|
||||
color: theme.palette.primary.dark,
|
||||
},
|
||||
footerLogo: {
|
||||
width: '15vw',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '4vw',
|
||||
},
|
||||
},
|
||||
footerLogoTT: {
|
||||
width: '70%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingBottom: theme.spacing(1),
|
||||
width: '100%'
|
||||
},
|
||||
},
|
||||
containerReactApp: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
height: 'auto',
|
||||
paddingTop: theme.spacing(4),
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
height: '100vh',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-around',
|
||||
height: '35vh',
|
||||
padding: theme.spacing(6, 1),
|
||||
position: 'relative',
|
||||
},
|
||||
},
|
||||
titleReactApp: {
|
||||
fontFamily: 'Dosis',
|
||||
fontSize: '1.5em',
|
||||
fontWeight: '600',
|
||||
paddingTop: theme.spacing(4),
|
||||
paddingLeft: theme.spacing(4),
|
||||
paddingRight: theme.spacing(4),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
order: 1,
|
||||
width: '20%',
|
||||
textAlign: 'left',
|
||||
},
|
||||
},
|
||||
buttonReactApp: {
|
||||
textTransform: 'inherit',
|
||||
margin: theme.spacing(0, 8),
|
||||
padding: theme.spacing(1.2, 2),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
order: 3,
|
||||
margin: 'inherit',
|
||||
marginRight: '5vw',
|
||||
},
|
||||
},
|
||||
imageReactApp: {
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 'auto',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '25%',
|
||||
order: 2,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
},
|
||||
},
|
||||
containerNewsletter: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: theme.spacing(6, 4),
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
function Footer(props) {
|
||||
const { classes } = props;
|
||||
return (
|
||||
<Fragment>
|
||||
{props.children}
|
||||
</Fragment>
|
||||
<div>
|
||||
<PWAInstallSnack >
|
||||
{({ initInstall }) =>
|
||||
<section
|
||||
className={classes.containerReactApp}
|
||||
>
|
||||
<Typography
|
||||
gutterBottom
|
||||
color="secondary"
|
||||
align="center"
|
||||
component="h3"
|
||||
className={classes.titleReactApp}
|
||||
>
|
||||
Installez l'application Talents Tube pour suivre vos candidatures
|
||||
</Typography>
|
||||
<Button
|
||||
aria-label='install'
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
onClick={initInstall}
|
||||
className={classes.buttonReactApp}
|
||||
>
|
||||
Installer l'application
|
||||
</Button>
|
||||
<img alt="télécharger-pwa" src='/static/images/newsletter/application.png' className={classes.imageReactApp} />
|
||||
</section>
|
||||
}
|
||||
</PWAInstallSnack>
|
||||
|
||||
<section className={classes.containerNewsletter}>
|
||||
<NewsLetterRegistration />
|
||||
</section>
|
||||
|
||||
<div className={classes.root}>
|
||||
<section className={classes.container}>
|
||||
<img alt="talents-tube" src='/static/images/logos/TalentsTube_logo-blanc.svg' className={classes.footerLogoTT} />
|
||||
<Link href="/Offres" >
|
||||
<a>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
Offre d'emploi
|
||||
</Typography>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/Entreprises" >
|
||||
<a>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
Entreprises
|
||||
</Typography>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/Help" >
|
||||
<a>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
Comment ça marche ?
|
||||
</Typography>
|
||||
</a>
|
||||
</Link>
|
||||
</section>
|
||||
<section className={classes.container}>
|
||||
<Typography color="secondary" component="p" className={classes.titleContainer}>
|
||||
Informations
|
||||
</Typography>
|
||||
|
||||
<Link href="/Cgu" >
|
||||
<a>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
CGU
|
||||
</Typography>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href="/MentionsLegales" >
|
||||
<a>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
Mentions légales
|
||||
</Typography>
|
||||
</a>
|
||||
</Link>
|
||||
<Link href="/Confidentialite" >
|
||||
<a>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
Politique de confidentialité et Protection <br />des données
|
||||
</Typography>
|
||||
</a>
|
||||
</Link>
|
||||
</section>
|
||||
<section className={classes.container}>
|
||||
<Typography color="secondary" component="p" className={classes.titleContainer}>
|
||||
Rencontrons-nous
|
||||
</Typography>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
3, rue Louis de Broglie
|
||||
</Typography>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
56 000 Vannes
|
||||
</Typography>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
02.30.05.06.31
|
||||
</Typography>
|
||||
<Typography gutterBottom color="secondary" component="p" className={classes.textContainer}>
|
||||
contact@talentstube.com
|
||||
</Typography>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
<section className={classes.containerRegion}>
|
||||
<Typography gutterBottom component="p" className={classes.textContainerRegion} align="center" >
|
||||
Entreprise soutenue par
|
||||
</Typography>
|
||||
<a href="https://www.bretagne.bzh/" target="_blank" className={classes.darkMenuItem} rel="noopener">
|
||||
<img alt="Région Bretagne" src='/static/images/logos/Logo-region.svg' className={classes.footerLogo} />
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const NextButton = ({ hasNext, onClick, style, className, text }) => hasNext ? (
|
||||
<button type="button" onClick={onClick} style={style} className={className}>Suivant</button>
|
||||
) :
|
||||
null;
|
||||
|
||||
export default NextButton;
|
||||
@@ -0,0 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
const PreviousButton = ({ hasPrevious, onClick, style, className, text }) => hasPrevious ? (
|
||||
<button type="button" onClick={onClick} style={style} className={className}>Précédent</button>
|
||||
) :
|
||||
null;
|
||||
|
||||
export default PreviousButton;
|
||||
@@ -1,43 +1,58 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Fragment, Children } from 'react';
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import Toolbar from '@material-ui/core/Toolbar';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { withStyles, getMuiTheme } from '@material-ui/core/styles';
|
||||
import MenuIcon from '@material-ui/icons/Menu';
|
||||
import Link from 'next/link'
|
||||
import Drawer from '@material-ui/core/Drawer';
|
||||
import ListItemIcon from '@material-ui/core/ListItemIcon';
|
||||
import Divider from '@material-ui/core/Divider';
|
||||
import SendIcon from '@material-ui/icons/Send';
|
||||
import CodeIcon from '@material-ui/icons/Code';
|
||||
import HomeIcon from '@material-ui/icons/Home';
|
||||
import FingerprintIcon from '@material-ui/icons/Fingerprint';
|
||||
import LocalBarIcon from '@material-ui/icons/LocalBar';
|
||||
import AccountCircle from '@material-ui/icons/AccountCircle';
|
||||
import HomeIcon from '@material-ui/icons/HomeOutlined';
|
||||
import OfferIcon from '@material-ui/icons/LocalOfferOutlined';
|
||||
import CompanyIcon from '@material-ui/icons/DomainOutlined';
|
||||
import HelpIcon from '@material-ui/icons/HelpOutline';
|
||||
import VideoIcon from '@material-ui/icons/OndemandVideoOutlined';
|
||||
import NotesIcon from '@material-ui/icons/NotesOutlined';
|
||||
import SupervisorAccountIcon from '@material-ui/icons/SupervisorAccount';
|
||||
import PersonIcon from '@material-ui/icons/Person';
|
||||
import PersonAddIcon from '@material-ui/icons/PersonAdd';
|
||||
import WorkOutlineIcon from '@material-ui/icons/WorkOutline';
|
||||
import AccountCircleOutlinedIcon from '@material-ui/icons/AccountCircleOutlined';
|
||||
import FolderSharedIcon from '@material-ui/icons/FolderShared';
|
||||
import AccountCircleIcon from '@material-ui/icons/AccountCircle';
|
||||
import SettingsIcon from '@material-ui/icons/Settings';
|
||||
import PowerSettingsNewIcon from '@material-ui/icons/PowerSettingsNew';
|
||||
import ArrowForward from '@material-ui/icons/ArrowForward';
|
||||
import List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import { ListItemText } from '@material-ui/core';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import Loader from './../templates/loader'
|
||||
import { connect } from 'react-redux'
|
||||
import { fetchCurrentUserRequest } from "../../actions/user";
|
||||
import { requestLogout } from "../../actions/auth";
|
||||
import Button from '@material-ui/core/Button';
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { withRouter } from "next/router";
|
||||
import ActiveLink from "./../atoms/Activelink";
|
||||
import axios from 'axios';
|
||||
import setAuthorizationHeader from "../../data/setAuthorizationHeader";
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
zIndex: '999',
|
||||
'& a': {
|
||||
textDecoration: 'none'
|
||||
}
|
||||
},
|
||||
|
||||
toolbar: {
|
||||
height: '64px',
|
||||
},
|
||||
grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
bar: {
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: -12,
|
||||
marginRight: 20,
|
||||
@@ -46,40 +61,237 @@ const styles = theme => ({
|
||||
menuLogo: {
|
||||
cursor: 'pointer',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width:'200px'
|
||||
width: '200px'
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width:'100px'
|
||||
width: '130px'
|
||||
},
|
||||
},
|
||||
title: {
|
||||
display: 'none',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'block',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
display: 'none',
|
||||
},
|
||||
marginRight: 15,
|
||||
cursor: 'pointer'
|
||||
menuAvatar: {
|
||||
width: '2.5em',
|
||||
borderRadius: '50%',
|
||||
marginTop: theme.spacing(2),
|
||||
marginLeft: theme.spacing(2),
|
||||
},
|
||||
sectionDesktop: {
|
||||
display: 'none',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
display: 'flex',
|
||||
},
|
||||
menuUserName: {
|
||||
color: theme.palette.menu.emailText,
|
||||
fontSize: '1.3rem',
|
||||
fontWeight: '500',
|
||||
},
|
||||
sectionMobile: {
|
||||
menuUserEmail: {
|
||||
color: theme.palette.menu.emailText,
|
||||
fontSize: '0.9rem',
|
||||
fontWeight: '200',
|
||||
},
|
||||
darkDivider: {
|
||||
background: 'whitesmoke',
|
||||
opacity: '0.1',
|
||||
},
|
||||
List: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
height: '100%',
|
||||
paddingTop: 0,
|
||||
paddingBottom: 0,
|
||||
'&>a:first-child': {
|
||||
minHeight: '64px',
|
||||
height: '64px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
display: 'none',
|
||||
'&>a:first-child': {
|
||||
height: '64px',
|
||||
},
|
||||
'&>a:first-child li': {
|
||||
height: '64px',
|
||||
alignItems: 'center',
|
||||
},
|
||||
},
|
||||
},
|
||||
fullList: {
|
||||
width: 'auto',
|
||||
cursor: 'pointer'
|
||||
cursor: 'pointer',
|
||||
paddingBottom: 0
|
||||
},
|
||||
lightFullList: {
|
||||
height: '100vh',
|
||||
backgroundColor: theme.palette.background.default,
|
||||
},
|
||||
darkFullList: {
|
||||
backgroundColor: theme.palette.background.dark,
|
||||
},
|
||||
lightMenuItem: {
|
||||
color: theme.palette.menu.lightText,
|
||||
textDecoration: 'none',
|
||||
},
|
||||
darkMenuItem: {
|
||||
color: theme.palette.menu.darkText,
|
||||
textDecoration: 'none',
|
||||
},
|
||||
darkMenuItemSpecific: {
|
||||
'&:hover,&:active': {
|
||||
color: theme.palette.primary.main,
|
||||
}
|
||||
},
|
||||
links: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
height: '100%',
|
||||
},
|
||||
linkGroup: {
|
||||
padding: theme.spacing(1.2, 0),
|
||||
paddingLeft: 0,
|
||||
},
|
||||
videothequeLinkGroup: {
|
||||
backgroundColor: theme.palette.background.videotheque,
|
||||
},
|
||||
blogLinkGroup: {
|
||||
backgroundColor: theme.palette.background.blog,
|
||||
},
|
||||
recruteurLinkGroup: {
|
||||
backgroundColor: theme.palette.background.recruteur,
|
||||
},
|
||||
rootListItem: {
|
||||
padding: theme.spacing(1, 2),
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
rootListItemIcon: {
|
||||
padding: theme.spacing(1, 2),
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
},
|
||||
ListItemUserConnected: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
ListItemText: {
|
||||
fontSize: '1em',
|
||||
},
|
||||
icon: {
|
||||
paddingRight: theme.spacing(2.5),
|
||||
},
|
||||
iconAccount: {
|
||||
color: theme.palette.primary.dark,
|
||||
},
|
||||
title: {
|
||||
color: theme.palette.secondary.main,
|
||||
fontSize: '1.5em'
|
||||
},
|
||||
email: {
|
||||
color: theme.palette.menu.emailText,
|
||||
fontSize: '0.9em'
|
||||
},
|
||||
button: {
|
||||
padding: theme.spacing(0.5, 1),
|
||||
textTransform: 'inherit',
|
||||
'& svg': {
|
||||
width: '0.7em',
|
||||
height: '0.7em',
|
||||
},
|
||||
},
|
||||
|
||||
active: {
|
||||
'& span, & svg': {
|
||||
color: theme.palette.primary.main + ' !important',
|
||||
},
|
||||
},
|
||||
|
||||
//DESKTOP PART
|
||||
|
||||
desktopMenuItem: {
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(0.4),
|
||||
},
|
||||
'& span.MuiTypography-root': {
|
||||
fontSize: '0.8rem',
|
||||
fontWeight: '500',
|
||||
},
|
||||
},
|
||||
desktopMenuList: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: 0,
|
||||
},
|
||||
appBarDesktopSection: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
appBarDesktopSection1: {
|
||||
padding: theme.spacing(0, 2),
|
||||
height: '100%',
|
||||
borderRight: 'solid 1px whitesmoke',
|
||||
},
|
||||
appBarDesktopSection2: {
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
padding: theme.spacing(0, 2),
|
||||
'& ul': {
|
||||
width: '95%',
|
||||
},
|
||||
'& ul>div': {
|
||||
display: 'flex',
|
||||
},
|
||||
'& ul > div:nth-child(1) a span': {
|
||||
color: theme.palette.primary.dark,
|
||||
'&:hover, &:active': {
|
||||
color: theme.palette.primary.main,
|
||||
}
|
||||
},
|
||||
'& .MuiListItemText-root': {
|
||||
padding: theme.spacing(0, 1),
|
||||
[theme.breakpoints.down('md')]: {
|
||||
padding: theme.spacing(0),
|
||||
},
|
||||
},
|
||||
'& a:last-child .MuiButtonBase-root span': {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
},
|
||||
appBarDesktopSection3: {
|
||||
padding: theme.spacing(0, 2),
|
||||
height: '100%',
|
||||
backgroundColor: theme.palette.primary.dark,
|
||||
'& a': {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
'& span, & svg': {
|
||||
color: theme.palette.secondary.main,
|
||||
},
|
||||
},
|
||||
toolbarDesktop: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
height: '64px',
|
||||
},
|
||||
desktopMenuButtonLogin: {
|
||||
background: theme.palette.primary.main,
|
||||
borderColor: theme.palette.primary.main,
|
||||
},
|
||||
rootListItemDesktop: {
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: theme.spacing(1),
|
||||
},
|
||||
},
|
||||
iconDesktop: {
|
||||
paddingRight: theme.spacing(2.5),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingRight: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
let urlEnv;
|
||||
|
||||
class Header extends React.Component {
|
||||
state = {
|
||||
@@ -90,20 +302,34 @@ class Header extends React.Component {
|
||||
toggle: true
|
||||
};
|
||||
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
toggleDrawer = (side, open) => () => {
|
||||
this.setState({
|
||||
[side]: open,
|
||||
});
|
||||
};
|
||||
|
||||
onRedirectAccount = e => {
|
||||
urlEnv = (process.env.NODE_ENV !== 'production') ? '//localhost/app_dev.php' : process.env.SERVER_URL;
|
||||
e.preventDefault();
|
||||
var redirectAccountUrl = urlEnv + "/api/talent?bearer=" + localStorage.getItem("ttJWT");
|
||||
window.open(redirectAccountUrl);
|
||||
}
|
||||
|
||||
toggle = () => this.setState(state => ({ toggle: !state.toggle }))
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Drawer anchor="left" open={this.state.left} onClose={this.toggleDrawer('left', false)}>
|
||||
@@ -113,75 +339,78 @@ class Header extends React.Component {
|
||||
onClick={this.toggleDrawer('left', false)}
|
||||
onKeyDown={this.toggleDrawer('left', false)}
|
||||
>
|
||||
<div className={classes.fullList}>
|
||||
<List>
|
||||
<Link href="/" prefetch >
|
||||
<a><ListItem>
|
||||
<img alt="talents-tube" src='/static/images/logos/Logo-header.svg' className={classes.menuLogo} />
|
||||
<div className={classes.fullList + ' ' + classes.lightFullList}>
|
||||
<List className={classes.List}>
|
||||
|
||||
<Link href="/" >
|
||||
<a className={classes.darkMenuItem}><ListItem classes={{ root: classes.rootListItem }}>
|
||||
<img alt="talents-tube" src='/static/images/logos/TalentsTube_logo-color.svg' className={classes.menuLogo} />
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
|
||||
|
||||
<Divider />
|
||||
<Link href="/" prefetch >
|
||||
<a><ListItem>
|
||||
<div className={classes.links}>
|
||||
<div className={classes.linkGroup}>
|
||||
<ActiveLink activeClassName={classes.active} href="/" >
|
||||
<a className={classes.darkMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<HomeIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Accueil'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</ActiveLink>
|
||||
<ActiveLink activeClassName={classes.active} href="/Offres" >
|
||||
<a className={classes.darkMenuItem + ' ' + classes.darkMenuItemSpecific}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<OfferIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary={`Offres d'emploi`}>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</ActiveLink>
|
||||
<ActiveLink activeClassName={classes.active} href="/Entreprises" >
|
||||
<a className={classes.darkMenuItem + ' ' + classes.darkMenuItemSpecific}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<CompanyIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Nos entreprises'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</ActiveLink>
|
||||
<ActiveLink activeClassName={classes.active} href="/Help" >
|
||||
<a className={classes.darkMenuItem + ' ' + classes.darkMenuItemSpecific}><ListItem classes={{ root: classes.rootListItem }}>
|
||||
<HelpIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Comment ça marche ?'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</ActiveLink>
|
||||
</div>
|
||||
|
||||
<ListItemText primary='Accueil'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Link href="/Offres" prefetch >
|
||||
<a><ListItem>
|
||||
<div>
|
||||
<div className={classes.videothequeLinkGroup}>
|
||||
<a href="https://www.talentstube.com/faire-offre-emploi-video" className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<VideoIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='CV vidéothèque'>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</a>
|
||||
</div>
|
||||
<div className={classes.blogLinkGroup}>
|
||||
<a href="https://blog.talentstube.com" className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<NotesIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Blog'>
|
||||
</ListItemText>
|
||||
</ListItem>
|
||||
</a>
|
||||
</div>
|
||||
<div className={classes.recruteurLinkGroup}>
|
||||
<a href="https://www.talentstube.com/faire-offre-emploi-video" className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<SupervisorAccountIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Recruteurs'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ListItemText primary="Offres d'emploi">
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Link href="/Companies" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Entreprises'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Link href="/help" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Comment ça marche ?'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Divider />
|
||||
|
||||
<Link href="/" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='CV vidéothèque'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Link href="https://blog.talentstube.com" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Blog'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Link href="/contact" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Contact'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Divider />
|
||||
|
||||
<Link href="https://www.talentstube.com/faire-offre-emploi-video" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Recruteurs'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
</div>
|
||||
</List>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
@@ -193,117 +422,234 @@ class Header extends React.Component {
|
||||
onClick={this.toggleDrawer('right', false)}
|
||||
onKeyDown={this.toggleDrawer('right', false)}
|
||||
>{!!this.props.loading &&
|
||||
(<div className="alert alert-info"><CircularProgress color="secondary" />Chargement...</div>
|
||||
(<Loader />
|
||||
)}
|
||||
{!!!this.props.loading && !!!this.props.fetched
|
||||
&&
|
||||
<div className={classes.fullList}>
|
||||
<div className={classes.fullList + ' ' + classes.darkFullList}>
|
||||
<List>
|
||||
<ListItem>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
</ListItem>
|
||||
<ListItem>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
<Link href="/login" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Connexion'>
|
||||
</ListItemText>
|
||||
<Link href="/" >
|
||||
<a className={classes.darkMenuItem + ' cypFrontPage'}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<img alt="talents-tube" src='/static/images/logos/TalentsTube_logo-blanc.svg' className={classes.menuLogo} />
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
{/* TODO */}
|
||||
<Link href="/login" prefetch >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Inscription'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link href="https://www.talentstube.com/faire-offre-emploi-video" >
|
||||
<a><ListItem>
|
||||
|
||||
<ListItemText primary='Espace recruteurs'>
|
||||
<Divider light={true} />
|
||||
|
||||
<div className={classes.linkGroup}>
|
||||
<Link href="/Login" >
|
||||
<a className={classes.lightMenuItem + ' cypConnexion'}><ListItem classes={{ root: classes.rootListItemIcon }} >
|
||||
<PersonIcon className={classes.icon} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Connexion'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
{/* TODO */}
|
||||
<Link href="/Login?inscription=true" >
|
||||
<a className={classes.lightMenuItem + ' cypRegister'}><ListItem classes={{ primary: classes.ListItemText }} classes={{ root: classes.rootListItem }}>
|
||||
<PersonAddIcon className={classes.icon} />
|
||||
<ListItemText primary='Inscription'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<Divider light={true} />
|
||||
|
||||
<div className={classes.linkGroup}>
|
||||
<a href="https://www.talentstube.com/faire-offre-emploi-video" className={classes.lightMenuItem}><ListItem classes={{ primary: classes.ListItemText }} classes={{ root: classes.rootListItemIcon }}>
|
||||
<WorkOutlineIcon className={classes.icon} />
|
||||
<ListItemText primary='Connexion recruteurs'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
</ListItem>
|
||||
</a>
|
||||
</div>
|
||||
</List>
|
||||
</div>}
|
||||
{!!!this.props.loading && !!this.props.fetched &&
|
||||
<Fragment>
|
||||
<ListItem>
|
||||
{this.props.user.firstname}
|
||||
{this.props.user.lastname}
|
||||
{this.props.user.email}
|
||||
{this.props.user.picture ?
|
||||
<img alt="talents-tube" src={`http://localhost/uploads/img/${this.props.user.picture}`} className={classes.menuAvatar} />
|
||||
:
|
||||
<img alt="talents-tube" src='/static/images/default/account-avatar-default.svg' className={classes.menuAvatar} />
|
||||
}
|
||||
<ListItem classes={{ root: classes.rootListItem }} className={classes.ListItemUserConnected}>
|
||||
<Typography gutterBottom component="h1" className={classes.menuUserName}>
|
||||
{this.props.user.firstname + " " + this.props.user.lastname}
|
||||
</Typography>
|
||||
|
||||
<Typography gutterBottom component="h3" className={classes.menuUserEmail} >
|
||||
{this.props.user.email}
|
||||
</Typography>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
|
||||
<Divider className={classes.darkDivider} />
|
||||
|
||||
{/* TODO:real links */}
|
||||
<Link href="/Candidatures" prefetch >
|
||||
<a><ListItem>
|
||||
<Link href="/Candidatures" >
|
||||
<a className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<FolderSharedIcon className={classes.icon} />
|
||||
<ListItemText primary='Mes candidatures'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Link href="/help" prefetch >
|
||||
<a><ListItem>
|
||||
<Link href="" >
|
||||
<a onClick={this.onRedirectAccount} className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<AccountCircleIcon className={classes.icon} />
|
||||
<ListItemText primary='Mon compte'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Link href="/help" prefetch >
|
||||
<a><ListItem>
|
||||
<Link href="/Parametres" >
|
||||
<a className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<SettingsIcon className={classes.icon} />
|
||||
<ListItemText primary='Paramètres'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
|
||||
<ListItem>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
onClick={() => this.props.requestLogout()}>Déconnexion</Button>
|
||||
</ListItem>
|
||||
<Link href="/" >
|
||||
<a onClick={() => {
|
||||
localStorage.removeItem("ttJWT");
|
||||
return this.props.requestLogout();
|
||||
}} className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
|
||||
<PowerSettingsNewIcon className={classes.icon} />
|
||||
<ListItemText primary='Déconnexion'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</Drawer>
|
||||
<AppBar position="static" className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton className={classes.menuButton} onClick={this.toggleDrawer('left', true)}
|
||||
color="inherit" aria-label="Open drawer">
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<div className={classes.grow} />
|
||||
|
||||
<img alt="talents-tube" src='/static/images/logos/Logo-header.svg' className={classes.menuLogo} />
|
||||
{isWidthUp('md', this.props.width) ?
|
||||
(<AppBar position="static" className={classes.appBar}>
|
||||
<Toolbar disableGutters={true} className={classes.toolbarDesktop}>
|
||||
<section className={classes.appBarDesktopSection + ' ' + classes.appBarDesktopSection1}>
|
||||
<Link href="/" >
|
||||
<a className={classes.darkMenuItem}>
|
||||
<ListItem classes={{ root: classes.rootListItemDesktop }}>
|
||||
<img alt="talents-tube" src='/static/images/logos/TalentsTube_logo-color.svg' className={classes.desktopMenuLogo} />
|
||||
</ListItem>
|
||||
</a>
|
||||
</Link>
|
||||
</section>
|
||||
<section className={classes.appBarDesktopSection + ' ' + classes.appBarDesktopSection2}>
|
||||
<IconButton className={classes.menuButton} onClick={this.toggleDrawer('left', true)}
|
||||
color="inherit" aria-label="Open drawer">
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
<div className={classes.grow} />
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Profile"
|
||||
aria-controls={'primary-search-account-menu'}
|
||||
aria-haspopup="true"
|
||||
onClick={this.toggleDrawer('right', true)}
|
||||
color="inherit"
|
||||
>
|
||||
<AccountCircle />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
<List className={classes.desktopMenuList}>
|
||||
<div>
|
||||
<ActiveLink activeClassName={classes.active} href="/Offres" >
|
||||
<a className={classes.desktopMenuItem}>
|
||||
<ListItemText primary="Offres d'emploi">
|
||||
</ListItemText>
|
||||
</a>
|
||||
</ActiveLink>
|
||||
<ActiveLink activeClassName={classes.active} href="/Entreprises" >
|
||||
<a className={classes.desktopMenuItem}>
|
||||
<ListItemText primary='Nos entreprises'>
|
||||
</ListItemText>
|
||||
</a>
|
||||
</ActiveLink>
|
||||
<ActiveLink activeClassName={classes.active} href="/Help" >
|
||||
<a className={classes.desktopMenuItem}>
|
||||
<ListItemText primary='Comment ça marche ?'>
|
||||
</ListItemText>
|
||||
</a>
|
||||
</ActiveLink>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{!!!this.props.loading && !!!this.props.fetched &&
|
||||
<Fragment>
|
||||
<Link href="/Login?inscription=true" >
|
||||
<a className={classes.desktopMenuItem}>
|
||||
<ListItem classes={{ root: classes.rootListItemDesktop }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
className={classes.button + ' ' + classes.desktopMenuButtonRegister}>
|
||||
<PersonAddIcon className={classes.iconDesktop} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Inscription'></ListItemText>
|
||||
</Button >
|
||||
</ListItem>
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<Link href="/Login" >
|
||||
<a className={classes.desktopMenuItem}>
|
||||
<ListItem classes={{ root: classes.rootListItemDesktop }}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
className={classes.button + ' ' + classes.desktopMenuButtonLogin}>
|
||||
<PersonIcon className={classes.iconDesktop} />
|
||||
<ListItemText classes={{ primary: classes.ListItemText }} primary='Connexion'></ListItemText>
|
||||
</Button >
|
||||
</ListItem>
|
||||
</a>
|
||||
</Link>
|
||||
</Fragment>
|
||||
}
|
||||
{!!!this.props.loading && !!this.props.fetched &&
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Profile"
|
||||
aria-controls={'primary-search-account-menu'}
|
||||
aria-haspopup="true"
|
||||
onClick={this.toggleDrawer('right', true)}
|
||||
className={classes.iconAccount + ' cypConnexionMenu'}
|
||||
>
|
||||
<AccountCircleOutlinedIcon />
|
||||
</IconButton>
|
||||
}
|
||||
</div>
|
||||
</List>
|
||||
</section>
|
||||
<section className={classes.appBarDesktopSection + ' ' + classes.appBarDesktopSection3}>
|
||||
|
||||
<a href="https://www.talentstube.com/faire-offre-emploi-video" className={classes.desktopMenuItem}>
|
||||
<ListItemText primary='Espace recruteurs'>
|
||||
</ListItemText>
|
||||
<ArrowForward className={classes.icon} />
|
||||
</a>
|
||||
</section>
|
||||
</Toolbar>
|
||||
</AppBar>)
|
||||
:
|
||||
(
|
||||
<AppBar position="static" className={classes.appBar}>
|
||||
<Toolbar className={classes.toolbar}>
|
||||
<IconButton className={classes.menuButton} onClick={this.toggleDrawer('left', true)}
|
||||
color="inherit" role="button">
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
<div className={classes.grow} />
|
||||
<Link href="/" >
|
||||
<a><img alt="talents-tube" src='/static/images/logos/TalentsTube_logo-color.svg' className={classes.menuLogo} /></a>
|
||||
</Link>
|
||||
<div className={classes.grow} />
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Profile"
|
||||
aria-controls={'primary-search-account-menu'}
|
||||
aria-haspopup="true"
|
||||
onClick={this.toggleDrawer('right', true)}
|
||||
className={classes.iconAccount + ' cypConnexionMenu'}
|
||||
|
||||
>
|
||||
<AccountCircleOutlinedIcon />
|
||||
</IconButton>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
)}
|
||||
</div >
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
user: state.user,
|
||||
@@ -311,5 +657,4 @@ function mapStateToProps(state) {
|
||||
loading: !!state.user.loading
|
||||
};
|
||||
}
|
||||
export default withStyles(styles)(connect(mapStateToProps, { fetchCurrentUserRequest, requestLogout })(Header));
|
||||
|
||||
export default (connect(mapStateToProps, { fetchCurrentUserRequest, requestLogout })(withStyles(styles)(withWidth()(Header))));
|
||||
@@ -2,22 +2,35 @@ import React, { Component, Fragment } from 'react'
|
||||
import GridList from '@material-ui/core/GridList';
|
||||
import GridListTile from '@material-ui/core/GridListTile';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
import ArrowLeft from '@material-ui/icons/ArrowBackIosSharp';
|
||||
import ArrowRight from '@material-ui/icons/ArrowForwardIosSharp';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Tile from './Tile';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden',
|
||||
backgroundColor: theme.palette.background.paper,
|
||||
width: '100%',
|
||||
},
|
||||
rootGridList: {
|
||||
margin: '0 !important',
|
||||
},
|
||||
gridList: {
|
||||
position: 'relative',
|
||||
flexWrap: 'nowrap',
|
||||
scrollBehavior: 'smooth',
|
||||
width: '100%',
|
||||
// Promote the list into his own layer on Chrome. This cost memory but helps keeping high FPS.
|
||||
transform: 'translateZ(0)',
|
||||
height: '100%',
|
||||
marginBottom: theme.spacing(4),
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': {
|
||||
width: 0,
|
||||
}
|
||||
},
|
||||
gridListTile: {
|
||||
display: 'flex',
|
||||
@@ -25,44 +38,114 @@ const styles = theme => ({
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
height: '100% !important',
|
||||
'&:first-child': {
|
||||
marginLeft: '3vw',
|
||||
//margin: '0 3vw',
|
||||
padding: '16px !important',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
margin: '0',
|
||||
paddingTop: '5vh',
|
||||
},
|
||||
},
|
||||
rootGridListTile: {
|
||||
margin: '3vw',
|
||||
|
||||
},
|
||||
tile: {
|
||||
width: '100% !important',
|
||||
overflow: 'visible !important',
|
||||
},
|
||||
anchor: {
|
||||
width: '100% !important',
|
||||
position: 'absolute',
|
||||
left: '16px',
|
||||
right: '16px',
|
||||
top: '-200px',
|
||||
visibility: 'hidden',
|
||||
},
|
||||
anchorPrevious: {
|
||||
left: '-16px',
|
||||
},
|
||||
anchorNext: {
|
||||
left: '16px',
|
||||
},
|
||||
icon: {
|
||||
width: '0.9em',
|
||||
height: '0.9em',
|
||||
padding: theme.spacing(1.5),
|
||||
color: theme.palette.secondary.main,
|
||||
background: theme.palette.primary.dark,
|
||||
borderRadius: '50%',
|
||||
},
|
||||
dNone: {
|
||||
visibility: 'hidden',
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
class HorizontalScroller extends Component {
|
||||
render() {
|
||||
const { classes } = this.props
|
||||
state = {
|
||||
defaultIndex: 2,
|
||||
direction: '',
|
||||
}
|
||||
|
||||
const children =(tile)=> React.Children.map(this.props.children, child => {
|
||||
handlePrevious = () => {
|
||||
this.setState({ direction: 'previous' })
|
||||
|
||||
if (this.state.defaultIndex == 8 || this.state.defaultIndex == 5) {
|
||||
this.setState({ defaultIndex: this.state.defaultIndex - 5 })
|
||||
}
|
||||
if (this.state.defaultIndex == 3) {
|
||||
this.setState({ defaultIndex: this.state.defaultIndex - 3 })
|
||||
}
|
||||
else if (this.state.defaultIndex == 0) {
|
||||
this.setState({ defaultIndex: this.state.defaultIndex + 8 })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
handleNext = () => {
|
||||
this.setState({ direction: 'next' })
|
||||
if (this.state.defaultIndex == 6 || this.state.defaultIndex == 7 || this.state.defaultIndex == 8) {
|
||||
this.setState({ defaultIndex: 0 })
|
||||
}
|
||||
else if (this.state.defaultIndex == 0 || this.state.defaultIndex == 3) {
|
||||
this.setState({ defaultIndex: this.state.defaultIndex + 5 })
|
||||
}
|
||||
else if (this.state.defaultIndex < 6) {
|
||||
this.setState({ defaultIndex: this.state.defaultIndex + 3 })
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, type } = this.props
|
||||
const { defaultIndex } = this.state
|
||||
const children = (tile) => React.Children.map(this.props.children, child => {
|
||||
return React.cloneElement(child, {
|
||||
tile
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div> {/* Ne pas mettre de Fragment */}
|
||||
<GridList className={classes.gridList} cols={1.2}>
|
||||
{this.props.data.map(tile => (
|
||||
<GridListTile className={classes.gridListTile} classes={{ root: classes.rootGridListTile, tile: classes.tile }} key={tile.title}>
|
||||
<Tile rowData={tile}>
|
||||
{ children(tile) }
|
||||
<div className={classes.root}> {/* Ne pas mettre de Fragment */}
|
||||
<Hidden smDown>
|
||||
<a className={(defaultIndex < 3) ? classes.dNone : ''} onClick={this.handlePrevious} href={'#' + type + defaultIndex}><ArrowLeft classes={{ root: classes.icon }}></ArrowLeft></a>
|
||||
</Hidden>
|
||||
<GridList className={classes.gridList} cols={isWidthUp('md', this.props.width) ? 3 : isWidthUp('sm', this.props.width) ? 2.4 : 1.15} classes={{ root: classes.rootGridList }}>
|
||||
{this.props.data && this.props.data.map((tile, index) => (
|
||||
|
||||
<GridListTile className={classes.gridListTile} classes={{ root: classes.rootGridListTile, tile: classes.tile }} key={index}>
|
||||
<Tile rowData={tile} key={index}>
|
||||
{(this.state.direction == 'previous') ? (<a id={type + index} className={classes.anchor + ' ' + classes.anchorPrevious}></a>) : (<a id={type + index} className={classes.anchor + ' ' + classes.anchorNext}></a>)
|
||||
}
|
||||
{children(tile)}
|
||||
</Tile>
|
||||
</GridListTile>
|
||||
|
||||
))}
|
||||
</GridList>
|
||||
<Hidden smDown>
|
||||
<a className={(defaultIndex > 5) ? classes.dNone : ''} onClick={this.handleNext} href={'#' + type + defaultIndex}><ArrowRight classes={{ root: classes.icon }}></ArrowRight></a>
|
||||
</Hidden>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(HorizontalScroller)
|
||||
export default withStyles(styles)(withWidth()(HorizontalScroller))
|
||||
|
||||
@@ -1,34 +1,179 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { connect } from "react-redux";
|
||||
import Validator from "validator";
|
||||
import { subscribeToNewsletterRequest } from "../../actions/user";
|
||||
import { Typography, Button } from '@material-ui/core';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
import _ from "lodash";
|
||||
import SendIcon from '@material-ui/icons/Send';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Snack from '../atoms/snack'
|
||||
|
||||
const styles = theme => ({
|
||||
textField: {
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
root: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
},
|
||||
text: {
|
||||
fontSize: '1.2em',
|
||||
fontWeight: '600',
|
||||
paddingRight: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(3),
|
||||
'& span': {
|
||||
color: theme.palette.primary.main
|
||||
},
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: '25%',
|
||||
paddingBottom: 0,
|
||||
},
|
||||
},
|
||||
textField: {
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 0,
|
||||
'& input': {
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
minWidth: '65vw',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
minWidth: 'inherit',
|
||||
},
|
||||
}
|
||||
},
|
||||
button: {
|
||||
padding: theme.spacing(2, 0),
|
||||
borderBottomLeftRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
boxShadown: '0px',
|
||||
backgroundColor: theme.palette.primary.dark,
|
||||
},
|
||||
sendIcon: {
|
||||
fill: theme.palette.background.grey,
|
||||
},
|
||||
newsletterForm: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
margin: theme.spacing(0, 4),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 'max-content',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
class NewsLetterRegistration extends Component {
|
||||
state = {
|
||||
data: {
|
||||
email: "",
|
||||
},
|
||||
success: false,
|
||||
errors: { message: "" },
|
||||
loading: false,
|
||||
tabIndex: 0,
|
||||
openSnack: false
|
||||
};
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
|
||||
this.setState({
|
||||
errors: !!!nextProps.errors ? { errors: { message: "" } } : nextProps.errors,
|
||||
loading: nextProps.loading,
|
||||
success: nextProps.success,
|
||||
openSnack: !_.isEmpty(nextProps.errors) || nextProps.success
|
||||
})
|
||||
}
|
||||
|
||||
onChange = e =>
|
||||
this.setState({
|
||||
data: { ...this.state.data, [e.target.name]: e.target.value }
|
||||
});
|
||||
|
||||
onSubmit = e => {
|
||||
e.preventDefault();
|
||||
const errors = this.validate(this.state.data);
|
||||
this.setState({ errors });
|
||||
if (Object.keys(errors).length === 0) {
|
||||
this.props.subscribeToNewsletterRequest(this.state.data)
|
||||
}
|
||||
};
|
||||
|
||||
submit = data => { this.props.login(data) }
|
||||
|
||||
validate = data => {
|
||||
const errors = {};
|
||||
if (!Validator.isEmail(data.email)) errors.email = "Invalid email";
|
||||
return errors;
|
||||
};
|
||||
|
||||
handleClose = e =>
|
||||
this.setState({
|
||||
openSnack: false
|
||||
});
|
||||
|
||||
render() {
|
||||
let {classes} = this.props
|
||||
const { classes } = this.props;
|
||||
const { data, success, errors, loading, openSnack } = this.state;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography gutterBottom component="h3">
|
||||
Recevez les dernières nouvelles de Talents Tube par e-mail
|
||||
<section className={classes.root}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
component="h3"
|
||||
className={classes.text}
|
||||
>
|
||||
Recevez les dernières nouvelles de <span>Talents Tube</span> par e-mail
|
||||
</Typography>
|
||||
<TextField
|
||||
id="outlined-bare"
|
||||
className={classes.textField}
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
inputProps={{ 'aria-label': 'bare' }}
|
||||
/>
|
||||
<Button variant="outlined" onClick={() => alert("Salut, tu veux faire un tuto ?")}>Ok</Button>
|
||||
</div>
|
||||
<form className={classes.newsletterForm} onSubmit={this.onSubmit}>
|
||||
<TextField
|
||||
id="outlined-with-placeholder"
|
||||
disabled={loading}
|
||||
label="Newsletter"
|
||||
name="email"
|
||||
value={data.email}
|
||||
onChange={this.onChange}
|
||||
type="email"
|
||||
placeholder="exemple@gmail.com"
|
||||
className={classes.textField}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
margin="dense"
|
||||
variant="filled"
|
||||
/>
|
||||
<Button type="submit"
|
||||
name="registerToNewsletter"
|
||||
disabled={loading}
|
||||
variant="contained"
|
||||
className={classes.button}
|
||||
>
|
||||
<SendIcon className={classes.sendIcon}/>
|
||||
</Button >
|
||||
</form>
|
||||
<Snack name={success ? 'snackSuccess' : 'snackWarning'}
|
||||
variant={success ? 'success' : 'warning'}
|
||||
message={success ? 'Inscription validée.' : errors.message}
|
||||
openSnack={openSnack}
|
||||
onClose={this.handleClose} />
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(NewsLetterRegistration)
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
openSnack: !_.isEmpty(ownProps.errors),
|
||||
loading: !!state.user.loading,
|
||||
success: state.user.NewsLetterRegistrationSuccess,
|
||||
errors: state.user.errors || ownProps.errors
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default (connect(mapStateToProps, { subscribeToNewsletterRequest })(withStyles(styles)(NewsLetterRegistration)));
|
||||
|
||||
@@ -0,0 +1,471 @@
|
||||
import React, { Fragment } from 'react'
|
||||
import Router from 'next/router'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import getConfig from 'next/config'
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import KeyboardArrowleftIcon from '@material-ui/icons/PlayArrowRounded';
|
||||
import IconText from '../atoms/IconText';
|
||||
import IconTextInline from './../atoms/IconTextInline';
|
||||
import SocialShare from './SocialShare';
|
||||
import SimpleCollapse from '../atoms/SimpleCollapse';
|
||||
import _ from "lodash/string";
|
||||
import Ar from "lodash/array";
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import Link from 'next/link'
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
import VideoVimeo from '../atoms/VideoVimeo'
|
||||
import OgHead from '../atoms/OgHead'
|
||||
|
||||
const styles = theme => ({
|
||||
primary: {
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
root: {
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
paddingTop: theme.spacing(20),
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
padding: theme.spacing(0, 3),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: '0 30vw',
|
||||
},
|
||||
},
|
||||
headerContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
margin: theme.spacing(1.5, 0),
|
||||
},
|
||||
headerContainerDesktop: {
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'space-between',
|
||||
margin: theme.spacing(1.5, 0),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: '16px 18vw',
|
||||
},
|
||||
'& button': {
|
||||
marginRight: 0,
|
||||
},
|
||||
},
|
||||
offerInfoContainer: {
|
||||
display: 'flex',
|
||||
},
|
||||
infoContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
margin: theme.spacing(0, 1.5),
|
||||
'& a': {
|
||||
textDecoration: 'none',
|
||||
color: theme.palette.primary.dark,
|
||||
}
|
||||
},
|
||||
logoContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 60,
|
||||
height: 60,
|
||||
backgroundColor: 'white',
|
||||
boxShadow: '0px 6px 15px rgba(30, 35, 39, 0.10)',
|
||||
border: 'solid 5px white',
|
||||
borderRadius: '5px',
|
||||
|
||||
},
|
||||
IconTextContainer: {
|
||||
paddingTop: theme.spacing(1.5, 0),
|
||||
},
|
||||
IconTextContainerDesktop: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
width: '100%',
|
||||
},
|
||||
contentContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
margin: theme.spacing(1.5, 0),
|
||||
},
|
||||
tagsContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexWrap: 'wrap',
|
||||
width: '100%',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
},
|
||||
shareContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignSelf: 'center',
|
||||
},
|
||||
discoverContainerCompany: {
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
paddingBottom: theme.spacing(2),
|
||||
'& section': {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
},
|
||||
},
|
||||
discoverContainerImage: {
|
||||
cursor: 'pointer',
|
||||
position: 'relative',
|
||||
'& img': {
|
||||
width: '100%',
|
||||
borderRadius: '5px',
|
||||
boxShadow: '0 6px 10px 0 rgba(0, 0, 0, 0.04), 0 1px 18px 0 rgba(0, 0, 0, 0.03), 0 3px 5px -1px rgba(0, 0, 0, 0.04)',
|
||||
'&:hover': {
|
||||
boxShadow: '0 6px 10px 0 rgba(0, 0, 0, 0.15), 0 1px 18px 0 rgba(0, 0, 0, 0.10), 0 3px 5px -1px rgba(0, 0, 0, 0.15)',
|
||||
transition: 'box-shadow 2s',
|
||||
}
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '100%',
|
||||
},
|
||||
},
|
||||
discoverContainerTerritory: {
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
paddingBottom: theme.spacing(10),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingTop: theme.spacing(2),
|
||||
paddingBottom: theme.spacing(8),
|
||||
},
|
||||
},
|
||||
discoverTerritoryContentContainer: {
|
||||
boxShadow: '0px 0px 10px rgba(30, 35, 39, 0.10)',
|
||||
},
|
||||
logo: {
|
||||
width: 'inherit',
|
||||
height: '100%',
|
||||
objectFit: 'contain',
|
||||
borderRadius: '0.25em',
|
||||
},
|
||||
MuiAvatarImg: {
|
||||
objectFit: 'contain',
|
||||
},
|
||||
title: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '500',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingTop: theme.spacing(2),
|
||||
},
|
||||
},
|
||||
offreTitle: {
|
||||
[theme.breakpoints.up('md')]: {
|
||||
fontSize: '1.2em',
|
||||
fontWeight: '600',
|
||||
},
|
||||
},
|
||||
discoverTitle: {
|
||||
padding: theme.spacing(3, 0),
|
||||
},
|
||||
contentText: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '300',
|
||||
padding: theme.spacing(1, 0),
|
||||
whiteSpace: 'pre-line',
|
||||
textAlign: 'justify',
|
||||
},
|
||||
contentCondition: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '300',
|
||||
textTransform: 'uppercase',
|
||||
textAlign: 'center',
|
||||
padding: theme.spacing(1.5, 0),
|
||||
},
|
||||
contentShare: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
},
|
||||
companyName: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '300',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
fontSize: '1em',
|
||||
},
|
||||
},
|
||||
iconText: {
|
||||
marginLeft: 0,
|
||||
justifyContent: 'flex-start',
|
||||
padding: theme.spacing(0.25, 0),
|
||||
},
|
||||
iconTextDesktop: {
|
||||
marginLeft: 0,
|
||||
justifyContent: 'flex-start',
|
||||
padding: theme.spacing(0.25, 0),
|
||||
width: 'inherit',
|
||||
},
|
||||
iconTextIcon: {
|
||||
color: theme.palette.offerlistitem.icon,
|
||||
fontSize: '1em',
|
||||
},
|
||||
iconTextText: {
|
||||
fontWeight: 'lighter',
|
||||
color: theme.palette.offerlistitem.text,
|
||||
fontSize: '0.7em',
|
||||
paddingLeft: theme.spacing(1),
|
||||
},
|
||||
iconTextTextDesktop: {
|
||||
fontWeight: 'lighter',
|
||||
color: theme.palette.offerlistitem.text,
|
||||
fontSize: '0.7em',
|
||||
paddingLeft: theme.spacing(1),
|
||||
width: '100%',
|
||||
},
|
||||
iconPlay: {
|
||||
fontSize: '3.5em',
|
||||
color: theme.palette.background.grey,
|
||||
position: 'absolute',
|
||||
top: 'calc(50% - 0.5em)',
|
||||
left: 'calc(50% - 0.5em)',
|
||||
},
|
||||
tags: {
|
||||
color: theme.palette.primary.dark,
|
||||
fontWeight: '300',
|
||||
backgroundColor: theme.palette.tags.main,
|
||||
textTransform: 'capitalize',
|
||||
margin: theme.spacing(1),
|
||||
},
|
||||
shareIcons: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
button: {
|
||||
textTransform: 'inherit',
|
||||
marginLeft: theme.spacing(8),
|
||||
marginRight: theme.spacing(8),
|
||||
padding: theme.spacing(1.2, 2),
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.primary.important,
|
||||
alignSelf: 'center',
|
||||
},
|
||||
chip: {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.secondary.important,
|
||||
padding: theme.spacing(1),
|
||||
margin: theme.spacing(1, 0),
|
||||
fontSize: '1em',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
marginRight: 'auto',
|
||||
},
|
||||
},
|
||||
videoOffre: {
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingBottom: '35%', /* 16:9 */
|
||||
},
|
||||
},
|
||||
videoTerritory: {
|
||||
paddingBottom: '56% !important',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
paddingBottom: '56% !important',
|
||||
}
|
||||
},
|
||||
territoryTitle: {
|
||||
fontWeight: '500',
|
||||
padding: theme.spacing(2),
|
||||
paddingBottom: 0,
|
||||
background: theme.palette.background.default,
|
||||
},
|
||||
territoryType: {
|
||||
fontWeight: '200',
|
||||
padding: theme.spacing(2),
|
||||
paddingTop: 0,
|
||||
background: theme.palette.background.default,
|
||||
borderBottomLeftRadius: '5px',
|
||||
borderBottomRightRadius: '5px',
|
||||
},
|
||||
})
|
||||
|
||||
const { publicRuntimeConfig } = getConfig()
|
||||
|
||||
function OffreDetail(props) {
|
||||
let { classes, offre, isCandidature } = props;
|
||||
const logoUrl = (offre.company ? publicRuntimeConfig.symphonyUrl + publicRuntimeConfig.cachedMediaUrl + offre.company.logo : "/static/images/defaultdefault_picture_company.svg/")
|
||||
const DefaultUrl = "/static/images/default/default_picture_company.svg"
|
||||
const imgUrl = (offre.company && offre.company.presentation_video && offre.company.presentation_video.thumbnails && offre.company.presentation_video.thumbnails[0] && offre.company.presentation_video.thumbnails[0].sizes[2]) ? offre.company.presentation_video.thumbnails[0].sizes[2].link : DefaultUrl
|
||||
const imgUrlDesktop = (offre.company && offre.company.presentation_video && offre.company.presentation_video.thumbnails && offre.company.presentation_video.thumbnails[0] && offre.company.presentation_video.thumbnails[0].sizes[4]) ? offre.company.presentation_video.thumbnails[0].sizes[4].link : DefaultUrl
|
||||
const socialShareUrl = typeof window !== 'undefined' ? window.location : publicRuntimeConfig.frontServerUrl + `/Offre/${offre.id}-${_.kebabCase(offre.title)}-${_.kebabCase(offre.location.gmap_address.name)}`;
|
||||
const entrepriseLink = (offre.company && `/Entreprise/${offre.company.id}-${_.kebabCase(offre.company.brand_name)}-${_.kebabCase(offre.location.gmap_address.name)}`);
|
||||
const territoryVideoUrlTab = (offre.territories && offre.territories[0] && offre.territories[0].territory) ? offre.territories[0].territory.presentation_video.cdn_url.split('/') : '';
|
||||
const territoryVideoId = territoryVideoUrlTab[2]
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<OgHead seo_title={offre.title}
|
||||
seo_description={offre.description}
|
||||
seo_url={socialShareUrl}
|
||||
image={imgUrlDesktop}
|
||||
></OgHead>
|
||||
{offre.video && (
|
||||
<Fragment>
|
||||
<Hidden smDown>
|
||||
<section className={classes.headerContainerDesktop}>
|
||||
<div className={classes.offerInfoContainer}>
|
||||
|
||||
<a href={entrepriseLink} >
|
||||
<div className={classes.logoContainer}>
|
||||
|
||||
<Avatar className={classes.logo} classes={{ img: classes.MuiAvatarImg }}
|
||||
src={logoUrl} alt="companyLogo" />
|
||||
|
||||
</div>
|
||||
</a>
|
||||
<div className={classes.infoContainer}>
|
||||
<Typography gutterBottom className={classes.offreTitle}>
|
||||
{offre.title}
|
||||
</Typography>
|
||||
<a href={entrepriseLink} >
|
||||
<Typography gutterBottom variant="subtitle1" className={classes.companyName}>
|
||||
{offre.company.brand_name && offre.company.brand_name}
|
||||
</Typography>
|
||||
</a>
|
||||
{offre.is_promoted && (
|
||||
<Chip label="Pourvue" className={classes.chip} />
|
||||
)}
|
||||
{/* ville */}
|
||||
</div>
|
||||
</div>
|
||||
{!!!offre.is_promoted && (
|
||||
<Link href={`/Reponse?adId=${offre.id}`}>
|
||||
<Button
|
||||
variant="contained"
|
||||
className={classes.button + ' cypButtonApply'}
|
||||
>Postuler</Button>
|
||||
</Link>
|
||||
)}
|
||||
</section>
|
||||
</Hidden>
|
||||
|
||||
<VideoVimeo {...isCandidature == 'true' ? 'classes={{ video: classes.videoOffre }}' : ''} videoId={Ar.last(_.split(offre.video.cdn_url, '/'))} autoplay='1' />
|
||||
|
||||
<div className={classes.container}>
|
||||
<Hidden mdUp>
|
||||
<section className={classes.headerContainer}>
|
||||
<div className={classes.logoContainer}>
|
||||
|
||||
<a href={entrepriseLink} >
|
||||
<Avatar className={classes.logo} classes={{ img: classes.MuiAvatarImg }}
|
||||
src={logoUrl} alt="companylogo" />
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div className={classes.infoContainer}>
|
||||
<Typography gutterBottom variant="h5" component="h1" className={classes.title}>
|
||||
{offre.title}
|
||||
</Typography>
|
||||
<a href={entrepriseLink} >
|
||||
<Typography gutterBottom variant="subtitle1" className={classes.text}>
|
||||
{offre.company.brand_name && offre.company.brand_name}
|
||||
</Typography>
|
||||
</a>
|
||||
{/* ville */}
|
||||
</div>
|
||||
</section>
|
||||
<section className={classes.IconTextContainer}>
|
||||
{offre.is_promoted && (<Chip label="Pourvue" className={classes.chip} />)}
|
||||
{offre.extra && (<IconText classes={{ root: classes.iconText, icon: classes.iconTextIcon, text: classes.iconTextText }} variant="assignment" text={offre.extra.contract_type} />)}
|
||||
{offre.location && offre.location.gmap_address && (<IconText classes={{ root: classes.iconText, icon: classes.iconTextIcon, text: classes.iconTextText }} variant="location_on" text={offre.location.gmap_address.formatted_address} />)}
|
||||
</section>
|
||||
</Hidden>
|
||||
|
||||
|
||||
<div className={classes.contentContainer}>
|
||||
<Typography gutterBottom className={classes.title}>
|
||||
Détails de l'offre d'emploi
|
||||
</Typography>
|
||||
<Hidden smDown>
|
||||
<section className={classes.IconTextContainerDesktop}>
|
||||
<IconTextInline classes={{ icon: classes.iconTextIcon, text: classes.iconTextText }} variant="assignment" text={offre.extra.contract_type} />
|
||||
<IconTextInline classes={{ icon: classes.iconTextIcon, text: classes.iconTextText }} variant="location_on" text={offre.location.gmap_address.formatted_address} />
|
||||
</section>
|
||||
</Hidden>
|
||||
<SimpleCollapse>
|
||||
<Typography gutterBottom className={classes.contentText}>
|
||||
{offre.description}
|
||||
</Typography>
|
||||
<section className={classes.tagsContainer}>
|
||||
{offre.public_tag.map((label, index) => {
|
||||
return (
|
||||
<Chip label={label} className={classes.tags} key={index} />
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
</SimpleCollapse>
|
||||
|
||||
{!offre.is_promoted && (
|
||||
<div className={classes.shareContainer}>
|
||||
<Link href={`/Reponse?adId=${offre.id}`}>
|
||||
<Button
|
||||
cyp="ButtonApply"
|
||||
variant="contained"
|
||||
className={classes.button}
|
||||
|
||||
>Postuler ! </Button>
|
||||
</Link>
|
||||
<Typography className={classes.contentCondition}>
|
||||
ou
|
||||
</Typography>
|
||||
<Typography gutterBottom className={classes.contentShare}>
|
||||
Partagez l'offre sur votre réseau
|
||||
</Typography>
|
||||
<section className={classes.ShareIcons}>
|
||||
<SocialShare url={socialShareUrl} />
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{offre.company.presentation_video && !offre.is_promoted && (
|
||||
<div className={classes.container + ' ' + classes.discoverContainerCompany}>
|
||||
<section>
|
||||
<Typography gutterBottom className={classes.title + ' ' + classes.discoverTitle} align="center">
|
||||
Découvrir l'entreprise <span className={classes.primary}>{offre.company.brand_name && offre.company.brand_name}</span>
|
||||
</Typography>
|
||||
|
||||
<a href={entrepriseLink}>
|
||||
<div className={classes.discoverContainerImage}>
|
||||
<img src={isWidthUp('sm', props.width) ? imgUrlDesktop : imgUrl} alt="company" />
|
||||
<KeyboardArrowleftIcon className={classes.iconPlay} />
|
||||
</div>
|
||||
</a>
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
{offre.territories && territoryVideoId && (
|
||||
<div className={classes.container + ' ' + classes.discoverContainerTerritory}>
|
||||
<section>
|
||||
<Typography gutterBottom className={classes.title + ' ' + classes.discoverTitle} align="center">
|
||||
Découvrir le <span className={classes.primary}>territoire</span>
|
||||
</Typography>
|
||||
<section className={classes.discoverTerritoryContentContainer}>
|
||||
<VideoVimeo classesOverride={classes.videoTerritory} videoId={territoryVideoId} autoplay='0' />
|
||||
<Typography className={classes.territoryTitle}>
|
||||
{(!!!offre.territories) ? offre.territories[0].territory.brand_name : ''}
|
||||
</Typography>
|
||||
<Typography className={classes.territoryType} gutterBottom>
|
||||
{(!!!offre.territories) ? offre.territories[0].territory.extra.terType : ''}
|
||||
</Typography>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
export default withStyles(styles)(withWidth()(OffreDetail));
|
||||
@@ -1,83 +1,112 @@
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import Router from 'next/router'
|
||||
import getConfig from 'next/config'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
import CardMedia from '@material-ui/core/CardMedia';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import IconText from './../atoms/IconText';
|
||||
import KeyboardArrowleftIcon from '@material-ui/icons/PlayArrowRounded';
|
||||
import Link from 'next/link';
|
||||
import _ from 'lodash';
|
||||
|
||||
const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
height: '50vh',
|
||||
minHeight: '21vh',
|
||||
maxHeight: '32vh',
|
||||
borderRadius: '5px',
|
||||
width: '100%',
|
||||
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0px 2px 4px rgba(30, 35, 39, 0.15)',
|
||||
transform: 'translateY(-0px)',
|
||||
transition: 'transform 0.25s, box-shadow ease-out',
|
||||
|
||||
'&:hover': {
|
||||
boxShadow: '0px 6px 16px rgba(30, 35, 39, 0.15)',
|
||||
transform: 'translateY(-5px)',
|
||||
transition: 'transform 0.5s, box-shadow 2s',
|
||||
'& .cardHover': {
|
||||
backgroundColor: 'rgba(0,0,0,0)',
|
||||
},
|
||||
},
|
||||
'& > div': {
|
||||
padding: '0 !important',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width : '30%',
|
||||
minHeight: '15vh',
|
||||
maxHeight: '20vh',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width : '40%',
|
||||
},
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width : '100%',
|
||||
},
|
||||
|
||||
margin: '1vh 2vh 1vh 2vh',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
margin: '1vh 0 1vh 0',
|
||||
},
|
||||
borderRadius: 0
|
||||
},
|
||||
details_container: {
|
||||
detailsContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '10vh 0 0 0',
|
||||
alignItems: 'flex-start',
|
||||
padding: theme.spacing(2),
|
||||
width: '75%',
|
||||
},
|
||||
company_title: {
|
||||
jobTitle: {
|
||||
fontSize: '1em',
|
||||
fontWeight: 'normal',
|
||||
textTransform: 'uppercase',
|
||||
fontWeight: 'bold',
|
||||
color: theme.palette.offerlistitem.title,
|
||||
},
|
||||
companyTitle: {
|
||||
fontSize: '0.8em',
|
||||
fontWeight: '300',
|
||||
color: theme.palette.offerlistitem.title,
|
||||
},
|
||||
content: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
width: '100%',
|
||||
padding: 0,
|
||||
},
|
||||
cover: {
|
||||
width: '100%',
|
||||
height: '50%',
|
||||
position: 'relative',
|
||||
width: '125px',
|
||||
height: '100%',
|
||||
backgroundPosition: 'center',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundBlendMode: 'darken',
|
||||
backgroundColor: 'rgba(30,35,39,0.25)',
|
||||
transition: 'background-color 1s',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '35%',
|
||||
},
|
||||
},
|
||||
logo_container: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 100,
|
||||
height: 100,
|
||||
|
||||
iconPlay: {
|
||||
fontSize: '2em',
|
||||
color: theme.palette.background.grey,
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -66%)',
|
||||
backgroundColor: 'white',
|
||||
boxShadow: '0 8px 6px -6px grey',
|
||||
top: 'calc(50% - 0.5em)',
|
||||
left: 'calc(50% - 0.5em)',
|
||||
},
|
||||
logo: {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
objectfit: 'contain',
|
||||
borderRadius: 0,
|
||||
iconText: {
|
||||
marginLeft: 0,
|
||||
justifyContent: 'flex-start',
|
||||
},
|
||||
iconTextIcon: {
|
||||
color: theme.palette.offerlistitem.icon,
|
||||
fontSize: '0.8em',
|
||||
},
|
||||
iconTextText: {
|
||||
color: theme.palette.offerlistitem.text,
|
||||
fontSize: '0.6em',
|
||||
paddingLeft: theme.spacing(1),
|
||||
},
|
||||
chip: {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.secondary.important,
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
@@ -86,51 +115,61 @@ class OffreRowComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
|
||||
const { classes, rowData } = this.props;
|
||||
const { classes, rowData, company } = this.props;
|
||||
|
||||
let companyR = company
|
||||
if (!!!company)
|
||||
companyR = rowData.company;
|
||||
else
|
||||
companyR = company;
|
||||
|
||||
const imgUrl = (rowData.video && rowData.video.thumbnails && rowData.video.thumbnails[0] && rowData.video.thumbnails[0].sizes[4]) ? rowData.video.thumbnails[0].sizes[4].link : publicRuntimeConfig.adListDefaultUrl
|
||||
|
||||
const imgUrl = (rowData.video.thumbnails[0] && rowData.video.thumbnails[0].sizes[4]) ? rowData.video.thumbnails[0].sizes[4].link : publicRuntimeConfig.adListDefaultUrl
|
||||
const logoUrl = publicRuntimeConfig.logoRootUrl + rowData.company.logo
|
||||
//rowData._embedded.company.business_name
|
||||
return (
|
||||
<Link href={`/Offre/${rowData.id}-${_.kebabCase(rowData.title)}-${_.kebabCase(rowData.location.gmap_address.name)}`} >
|
||||
<Card className={classes.card + ' cypCardOffre'} >
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={ classes.cover + ' cardHover'}
|
||||
image={imgUrl}
|
||||
title={rowData.title}
|
||||
>
|
||||
<KeyboardArrowleftIcon className={classes.iconPlay} />
|
||||
</CardMedia>
|
||||
|
||||
<Card className={classes.card} onClick={() => Router.push(`/Offre?id=${rowData.id}`)}>
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={imgUrl}
|
||||
title={rowData.title}
|
||||
/>
|
||||
<div className={classes.detailsContainer}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
className={classes.jobTitle}>
|
||||
{rowData.title}
|
||||
</Typography>
|
||||
<Typography
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
className={classes.companyTitle}>
|
||||
{!!companyR.brand_name?companyR.brand_name:"-"}
|
||||
</Typography>
|
||||
<div>
|
||||
{rowData.is_promoted && (
|
||||
<Chip label="Pourvue" className={classes.chip} />
|
||||
)}
|
||||
|
||||
<div className={classes.logo_container}>
|
||||
<Avatar className={classes.logo} src={logoUrl} />
|
||||
</div>
|
||||
{!rowData.is_promoted && (
|
||||
<Fragment>
|
||||
<IconText classes={{ root: classes.iconText, icon: classes.iconTextIcon, text: classes.iconTextText }} variant="location_on" text={rowData.extra.contract_type} />
|
||||
<IconText classes={{ root: classes.iconText, icon: classes.iconTextIcon, text: classes.iconTextText }} variant="business_center" text={rowData.location.gmap_address.formatted_address} />
|
||||
</Fragment>
|
||||
)}
|
||||
|
||||
<div className={classes.details_container}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
align="center"
|
||||
className={classes.company_title}>
|
||||
{rowData.company.business_name}
|
||||
</Typography>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.job_title}>
|
||||
{rowData.title}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Chip label={rowData.extra.contract_type} className={classes.chip} />
|
||||
<Chip label={rowData.location.gmap_address.address_components[0].long_name} className={classes.chip} />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,85 +8,124 @@ import CardMedia from '@material-ui/core/CardMedia';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Collapse from '@material-ui/core/Collapse';
|
||||
|
||||
const styles = theme => ({
|
||||
Snackbar: {
|
||||
zIndex: 10001,
|
||||
},
|
||||
cardSnack: {
|
||||
maxWidth: 345,
|
||||
},
|
||||
media: {
|
||||
height: 140,
|
||||
}
|
||||
},
|
||||
askToInstallText: {
|
||||
fontSize: '1.4em',
|
||||
fontWeight: '500',
|
||||
fontFamily: 'Dosis',
|
||||
},
|
||||
installApp: {
|
||||
width: '100%',
|
||||
},
|
||||
installStepText: {
|
||||
fontSize: '1.2em',
|
||||
fontWeight: '500',
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
button: {
|
||||
color: theme.palette.primary.main,
|
||||
borderColor: theme.palette.primary.main,
|
||||
width: '50%',
|
||||
},
|
||||
buttonCompris: {
|
||||
width: '100%',
|
||||
},
|
||||
});
|
||||
|
||||
class PWAInstallSnack extends Component {
|
||||
state = {
|
||||
openSnack: false,
|
||||
buttonDisplay: true
|
||||
buttonDisplay: false
|
||||
}
|
||||
|
||||
isAvailable = () =>{
|
||||
console.log(this.buttonDisplay)
|
||||
isAvailable = () => {
|
||||
return this.state.buttonDisplay;
|
||||
}
|
||||
|
||||
initInstall = () =>
|
||||
{
|
||||
this.setState({openSnack: true})
|
||||
initInstall = () => {
|
||||
this.setState({ openSnack: true })
|
||||
}
|
||||
getStateAndHelpers() {
|
||||
return {
|
||||
on: this.state.openSnack,
|
||||
initInstall: this.initInstall,
|
||||
on: this.state.openSnack,
|
||||
initInstall: this.initInstall,
|
||||
}
|
||||
}
|
||||
}
|
||||
iOS() {
|
||||
|
||||
var iDevices = [
|
||||
'iPad Simulator',
|
||||
'iPhone Simulator',
|
||||
'iPod Simulator',
|
||||
'iPad',
|
||||
'iPhone',
|
||||
'iPod'
|
||||
'iPad Simulator',
|
||||
'iPhone Simulator',
|
||||
'iPod Simulator',
|
||||
'iPad',
|
||||
'iPhone',
|
||||
'iPod'
|
||||
];
|
||||
|
||||
|
||||
if (!!navigator.platform) {
|
||||
while (iDevices.length) {
|
||||
if (navigator.platform === iDevices.pop()){ return true; }
|
||||
}
|
||||
while (iDevices.length) {
|
||||
if (navigator.platform === iDevices.pop()) { return true; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
BeforePromptInstallTrigger() {
|
||||
this.setState({ buttonDisplay: true })
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (window !== undefined) {
|
||||
window.addEventListener('beforeinstallprompt', () => {
|
||||
this.BeforePromptInstallTrigger()
|
||||
})
|
||||
|
||||
if (!!window.deferredPrompt)
|
||||
this.setState({ buttonDisplay: true });
|
||||
|
||||
if (typeof window !== 'undefined') {
|
||||
// don't show if we are in App
|
||||
if (window.navigator.standalone ||
|
||||
window.matchMedia('(display-mode: standalone)').matches)
|
||||
{
|
||||
console.log('standalone')
|
||||
this.setState({ openSnack: false, buttonDisplay: false })
|
||||
window.matchMedia('(display-mode: standalone)').matches) {
|
||||
this.setState({ openSnack: false, buttonDisplay: false })
|
||||
}
|
||||
else if(this.IsSafari())
|
||||
{
|
||||
console.log('safari')
|
||||
this.setState({ openSnack: false, buttonDisplay: false })
|
||||
// else if (window.deferredPrompt === null) {
|
||||
// this.setState({ openSnack: false, buttonDisplay: false })
|
||||
// }
|
||||
else if (this.IsSafari()) {
|
||||
this.setState({ openSnack: false, buttonDisplay: false })
|
||||
|
||||
if(this.iOS())
|
||||
this.setState({ openSnack: false, buttonDisplay: true })
|
||||
|
||||
if (this.iOS())
|
||||
{
|
||||
this.setState({ openSnack: false, buttonDisplay: true })}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
window.removeEventListener('beforeinstallprompt', () => {
|
||||
this.BeforePromptInstallTrigger()
|
||||
})
|
||||
}
|
||||
|
||||
//A2HS 'Add 2 Home Screen'
|
||||
PromptA2HS = (event, reason) => {
|
||||
|
||||
this.setState({ openSnack: false })
|
||||
console.log('prompted')
|
||||
if (window !== undefined) {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.promptInstall();
|
||||
}
|
||||
}
|
||||
@@ -105,38 +144,40 @@ class PWAInstallSnack extends Component {
|
||||
const { classes } = this.props;
|
||||
var safariSnack = (<Card className={classes.cardSnack}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
image="/static/images/help/addToHomeFull.png"
|
||||
title="homeScreen"
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="h2">
|
||||
-> les PWA doivent encore s'installer manuellement sous IOS
|
||||
<Typography gutterBottom component="h2">
|
||||
Suivez les instructions ci -dessous :
|
||||
</Typography>
|
||||
<Typography gutterBottom align="center" component="h2" className={ classes.installStepText }>
|
||||
Etape 1 :
|
||||
</Typography>
|
||||
<img alt="installer-pwa" src='/static/images/help/safari-actions.png' className={classes.installApp} />
|
||||
<Typography gutterBottom align="center" component="h2" className={ classes.installStepText }>
|
||||
Etape 2 :
|
||||
</Typography>
|
||||
<img alt="installer-pwa" src='/static/images/help/safari-install-app.png' className={classes.installApp} />
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button name="compris" size="small" color="secondary" onClick={this.handleClose}>
|
||||
j'ai compris
|
||||
<Button name="compris" className={classes.buttonCompris} color="primary" onClick={this.handleClose}>
|
||||
j'ai compris
|
||||
</Button>
|
||||
</CardActions>
|
||||
</CardActions>
|
||||
</CardActionArea>
|
||||
|
||||
</Card>);
|
||||
|
||||
var usualSnack = (<Card className={classes.cardSnack}>
|
||||
<CardActionArea>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="h2">
|
||||
<Typography gutterBottom component="h2" className={ classes.askToInstallText }>
|
||||
Voulez-vous installer notre application ?
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<Button name='oui' size="small" color="secondary" onClick={this.PromptA2HS}>
|
||||
<Button className={classes.button} color="primary" name='oui' size="small" onClick={this.PromptA2HS}>
|
||||
oui
|
||||
</Button>
|
||||
<Button name='non' size="small" color="secondary" onClick={this.handleClose}>
|
||||
<Button className={classes.button} color="primary" name='non' size="small" onClick={this.handleClose}>
|
||||
non
|
||||
</Button>
|
||||
</CardActions>
|
||||
@@ -144,8 +185,9 @@ class PWAInstallSnack extends Component {
|
||||
|
||||
|
||||
|
||||
return <Fragment>
|
||||
return <Fragment>
|
||||
<Snackbar
|
||||
className={ classes.Snackbar }
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
open={this.state.openSnack}
|
||||
onClose={this.handleClose}
|
||||
@@ -154,9 +196,11 @@ class PWAInstallSnack extends Component {
|
||||
}}>
|
||||
{this.IsSafari() ? safariSnack : usualSnack}
|
||||
</Snackbar>
|
||||
{this.state.buttonDisplay && this.props.children(this.getStateAndHelpers())}
|
||||
</Fragment>
|
||||
|
||||
<Collapse in={this.state.buttonDisplay}>
|
||||
{this.state.buttonDisplay && this.props.children(this.getStateAndHelpers())}
|
||||
</Collapse>
|
||||
</Fragment>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import dynamic from 'next/dynamic'
|
||||
import Button from '@material-ui/core/Button';
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import axios from 'axios'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import setAuthorizationHeader from "../../data/setAuthorizationHeader";
|
||||
import 'videojs/dist/video-js.css';
|
||||
import 'videojs-record/dist/css/videojs.record.css';
|
||||
@@ -16,26 +15,66 @@ const RTCRecorder = dynamic(() => import('RecordRTC'), {
|
||||
ssr: false
|
||||
})
|
||||
|
||||
const videoJsOptions = {
|
||||
controls: true,
|
||||
|
||||
fluid: true,
|
||||
plugins: {
|
||||
/*
|
||||
// wavesurfer section is only needed when recording audio-only
|
||||
wavesurfer: {
|
||||
src: 'live',
|
||||
waveColor: '#36393b',
|
||||
progressColor: 'black',
|
||||
debug: true,
|
||||
cursorWidth: 1,
|
||||
msDisplayMax: 20,
|
||||
hideScrollbar: true
|
||||
},
|
||||
*/
|
||||
record: {
|
||||
audio: true,
|
||||
video: true,
|
||||
maxLength: 600,
|
||||
//debug: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
margin: theme.spacing.unit,
|
||||
margin: theme.spacing(1),
|
||||
maxWidth: '400px', [theme.breakpoints.down('md')]: {
|
||||
maxWidth: `${100 - (theme.spacing.unit)}vw`
|
||||
maxWidth: `${100 - (theme.spacing(1))}vw`
|
||||
},
|
||||
sendContainer: {
|
||||
textAlign: 'center'
|
||||
},
|
||||
button: {
|
||||
textTransform: 'inherit',
|
||||
marginLeft: theme.spacing(8),
|
||||
marginRight: theme.spacing(8),
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(2),
|
||||
padding: theme.spacing(1.2, 2),
|
||||
//borderRadius: '3px',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
margin: 'auto',
|
||||
},
|
||||
|
||||
margin: theme.spacing.unit,
|
||||
alignSelf: 'flex-end'
|
||||
},
|
||||
}
|
||||
})
|
||||
class Recorder extends Component {
|
||||
|
||||
state={
|
||||
canSend:false
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
var record = (await import('videojs-record')).default
|
||||
// instantiate Video.js
|
||||
this.player = videojs(this.videoNode, this.props, () => {
|
||||
this.player = videojs(this.videoNode, videoJsOptions, () => {
|
||||
// print version information at startup
|
||||
var version_info = 'Using video.js ' + videojs.VERSION +
|
||||
' with videojs-record ' + videojs.getPluginVersion('record') +
|
||||
@@ -45,12 +84,14 @@ class Recorder extends Component {
|
||||
|
||||
// device is ready
|
||||
this.player.on('deviceReady', () => {
|
||||
console.log('device is ready!');
|
||||
console.info('device is ready!');
|
||||
this.setState({canSend:false});
|
||||
});
|
||||
|
||||
// user clicked the record button and started recording
|
||||
this.player.on('startRecord', () => {
|
||||
console.log('started recording!');
|
||||
this.setState({canSend:false});
|
||||
});
|
||||
|
||||
// user completed recording and stream is available
|
||||
@@ -58,19 +99,22 @@ class Recorder extends Component {
|
||||
// recordedData is a blob object containing the recorded data that
|
||||
// can be downloaded by the user, stored on server etc.
|
||||
console.log('finished recording: ', this.player.recordedData);
|
||||
this.setState({canSend:true});
|
||||
});
|
||||
|
||||
// error handling
|
||||
this.player.on('error', (element, error) => {
|
||||
console.warn(error);
|
||||
this.setState({canSend:false});
|
||||
});
|
||||
|
||||
this.player.on('deviceError', () => {
|
||||
console.error('device error:', this.player.deviceErrorCode);
|
||||
this.setState({canSend:false});
|
||||
});
|
||||
}
|
||||
|
||||
// destroy player on unmount
|
||||
//destroy player on unmount
|
||||
componentWillUnmount() {
|
||||
if (this.player) {
|
||||
this.player.dispose();
|
||||
@@ -78,24 +122,11 @@ class Recorder extends Component {
|
||||
}
|
||||
|
||||
sendBlob = () => {
|
||||
console.log('accessing recording: ', this.player.recordedData)
|
||||
// console.log('accessing recording: ', this.player.recordedData)
|
||||
var formData = new FormData();
|
||||
formData.append('upload', this.player.recordedData, "myupload");
|
||||
// formData.append('user', 7);//TODO: taking from auth
|
||||
|
||||
setAuthorizationHeader('eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJpYXQiOjE1NTk4OTE4ODIsImV4cCI6MTU1OTg5NTQ4Miwicm9sZXMiOlsiUk9MRV9UQUxFTlQiLCJST0xFX1VTRVIiXSwidXNlcm5hbWUiOiJ0aG9tYXMuYmVycm9kQGUtZGVjbGljLmNvbSJ9.dnXiifet5SIyZf1zOBnu6RvVqA6k-aT16O_YNy6ZAhRZUuQtqP21enNkug0KxLEFMy91gcj0gkGuETqMwcHBL_xkx7rJvb2dLoOo0yduZxJLzngWKroCZE152t-V5Edn2Gje3CHMGqTZLwjBowEgUTpgfyTE9IaJn-q2pizukWBzjmfmdz5mYFgoIQrsjThkPxRH8cNOTit0bj6SebjsCCsPb_jdM6rek03qtwELz2ABJaHJU1FiGGTLYtcF3k32AGlRwa5P9etKwQUrz0AL5q3lroKgbtwhxY3cKyRKAo-o68s3v12_T6R86WqEegOx6TyBb06GJhTcP1MIGBMMgNIm5GxSi0U0RvRDmLuFtsuZv5wBUZqkpth8Z_dK5H003k67XjLrYC4TnMhQXxUGV6Eji3lkCY5fOWbQjwXRO9R_rPCNSiPEZj0o_-ach0a4Y5nLhzVlnb3J0plpb4lwpN3D9t9FNfizKQ5VX9eLoozrzkI-apIbV-Z0is8i6j4yt7R24PR9dyreQXZ3-e_tEwu5UTKi-zYAaHaHrRgCSjWg4d6ozuyXQmzi64d1-Fnx6IkU_KtXtUYRVsZhQUvJpri2x1szLLM-Hqdsh2Ljuu5qDdJrHRKeG1gZeKWPfvZweZ3SWrfvlNZHKNWX-2pmEvz9sJdpsAO-NnmTEUVrpzY')
|
||||
|
||||
axios.post("http://localhost/app_dev.php/api/media",
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': `multipart/form-data;`
|
||||
},
|
||||
withCredentials: true
|
||||
})
|
||||
.then(res => console.log(res)).catch(err => console.log(err))
|
||||
|
||||
console.log('end send Blob')
|
||||
this.props.uploader(formData);
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -105,12 +136,12 @@ class Recorder extends Component {
|
||||
<div data-vjs-player>
|
||||
<video id="myVideo" ref={node => this.videoNode = node} className="video-js vjs-default-skin" playsInline></video>
|
||||
</div>
|
||||
<div>
|
||||
<Button type="submit" variant="outlined" color="secondary"
|
||||
<div className={classes.sendContainer}>
|
||||
{ this.state.canSend && <Button type="submit" variant="outlined" color="primary"
|
||||
className={classes.button}
|
||||
onClick={this.sendBlob}>
|
||||
Send
|
||||
</Button>
|
||||
Envoyer
|
||||
</Button>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,21 +1,28 @@
|
||||
import React, {Fragment} from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { connect } from "react-redux";
|
||||
import Validator from "validator";
|
||||
import {FormLabel, FormHelperText,InputLabel, Input,TextField, FormGroup, FormControlLabel, Checkbox, Button, Typography, Link } from '@material-ui/core';
|
||||
|
||||
import { TextField, FormControlLabel, Checkbox, Button, Typography, Link } from '@material-ui/core';
|
||||
import Visibility from '@material-ui/icons/Visibility';
|
||||
import VisibilityOff from '@material-ui/icons/VisibilityOff';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { registerUserRequest, subscribeToNewsletterRequest } from "../../actions/user"
|
||||
import SocialButtonsAuth from '../atoms/SocialButtonsAuth';
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
paddingTop: theme.spacing(4),
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
padding: '5vw 10vw',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
padding: '5vw 25vw',
|
||||
},
|
||||
},
|
||||
textField: {
|
||||
width: '100%'
|
||||
@@ -24,13 +31,75 @@ const styles = theme => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
input50: {
|
||||
width: '50%',
|
||||
formText: {
|
||||
padding: theme.spacing(0, 2),
|
||||
paddingTop: theme.spacing(3),
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '300',
|
||||
},
|
||||
checkboxError:{
|
||||
color:'red'
|
||||
}
|
||||
formLink: {
|
||||
paddingLeft: theme.spacing(1),
|
||||
fontWeight: '500',
|
||||
color: theme.palette.primary.dark,
|
||||
},
|
||||
checkbox: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
|
||||
'& .MuiFormControlLabel-label': {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '500',
|
||||
color: theme.palette.primary.dark,
|
||||
}
|
||||
},
|
||||
checkboxError: {
|
||||
'& .MuiFormControlLabel-label': {
|
||||
color: 'red'
|
||||
}
|
||||
},
|
||||
conditionalText: {
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
color: theme.palette.primary.conditional,
|
||||
textTransform: 'uppercase',
|
||||
|
||||
'&:before': {
|
||||
content: '""',
|
||||
flex: '1 1',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.primary.conditional,
|
||||
marginRight: '10px',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
},
|
||||
'&:after': {
|
||||
content: '""',
|
||||
flex: '1 1',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: theme.palette.primary.conditional,
|
||||
marginLeft: '10px',
|
||||
marginTop: 'auto',
|
||||
marginBottom: 'auto',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
textTransform: 'inherit',
|
||||
marginLeft: theme.spacing(10),
|
||||
marginRight: theme.spacing(10),
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(2),
|
||||
padding: theme.spacing(1.2, 1),
|
||||
borderRadius: '3px',
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.primary.important,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
margin: 'auto',
|
||||
},
|
||||
},
|
||||
buttonImportant: {
|
||||
marginTop: theme.spacing(6),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
});
|
||||
|
||||
class Register extends React.Component {
|
||||
@@ -43,9 +112,15 @@ class Register extends React.Component {
|
||||
checkNewsletter: false,
|
||||
checkCGU: false,
|
||||
},
|
||||
errors: {},
|
||||
errors: {
|
||||
email:null,
|
||||
password:null,
|
||||
firstname:null,
|
||||
lastname:null,
|
||||
checkCGU:null
|
||||
},
|
||||
loading: false,
|
||||
showPassword:false
|
||||
showPassword: false
|
||||
}
|
||||
|
||||
validate = data => {
|
||||
@@ -55,19 +130,17 @@ class Register extends React.Component {
|
||||
if (!!!data.firstname) errors.firstname = "Requis";
|
||||
if (!!!data.lastname) errors.lastname = "Requis";
|
||||
if (!data.checkCGU) errors.checkCGU = "Requis";
|
||||
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
onRegister = e => {
|
||||
e.preventDefault();
|
||||
const errors = this.validate(this.state.data);
|
||||
console.log('onSubmit')
|
||||
console.log(errors)
|
||||
|
||||
this.setState({ errors: errors });
|
||||
|
||||
if (Object.keys(errors).length === 0) {
|
||||
this.setState({ loading: true });
|
||||
|
||||
this.props.registerUserRequest(this.state.data)
|
||||
|
||||
@@ -77,7 +150,7 @@ class Register extends React.Component {
|
||||
|
||||
};
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
errors: nextProps.errors,
|
||||
loading: nextProps.loading
|
||||
@@ -88,7 +161,7 @@ class Register extends React.Component {
|
||||
data: { ...this.state.data, [e.target.name]: e.target.value }
|
||||
});
|
||||
handleCheckBoxChange = name => event => {
|
||||
this.setState({data:{ ...this.state.data, [name]: event.target.checked }});
|
||||
this.setState({ data: { ...this.state.data, [name]: event.target.checked } });
|
||||
};
|
||||
|
||||
handleChangePasswordVisibility = prop => event => {
|
||||
@@ -102,38 +175,34 @@ class Register extends React.Component {
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const { data, errors } = this.state;
|
||||
console.log('render errors');
|
||||
console.log(errors);
|
||||
console.log(this.state);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
|
||||
<form className={classes.form}>
|
||||
<FormGroup row>
|
||||
<TextField
|
||||
value={data.firstname}
|
||||
className={classes.input50}
|
||||
onChange={this.onChange}
|
||||
id="prenom"
|
||||
name="firstname"
|
||||
label="Prénom"
|
||||
margin="normal"
|
||||
required
|
||||
error={!!errors.firstname}
|
||||
/>
|
||||
<TextField
|
||||
value={data.lastname}
|
||||
className={classes.input50}
|
||||
onChange={this.onChange}
|
||||
id="nom"
|
||||
name="lastname"
|
||||
label="Nom"
|
||||
margin="normal"
|
||||
required
|
||||
error={!!errors.lastname}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<TextField
|
||||
value={data.lastname}
|
||||
onChange={this.onChange}
|
||||
id="nom"
|
||||
name="lastname"
|
||||
label="Nom"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
required
|
||||
error={!!errors.lastname}
|
||||
/>
|
||||
<TextField
|
||||
value={data.firstname}
|
||||
onChange={this.onChange}
|
||||
id="prenom"
|
||||
name="firstname"
|
||||
label="Prénom"
|
||||
margin="normal"
|
||||
variant="outlined"
|
||||
required
|
||||
error={!!errors.firstname}
|
||||
/>
|
||||
|
||||
<TextField
|
||||
value={data.email}
|
||||
@@ -143,80 +212,93 @@ class Register extends React.Component {
|
||||
label="Email"
|
||||
margin="normal"
|
||||
type="email"
|
||||
variant="outlined"
|
||||
required
|
||||
error={!!errors.email}
|
||||
autoComplete="username email"
|
||||
/>
|
||||
<FormControl>
|
||||
<InputLabel htmlFor="adornment-password">Password *</InputLabel>
|
||||
<Input
|
||||
name="password"
|
||||
id="adornment-password"
|
||||
type={this.state.showPassword ? 'text' : 'password'}
|
||||
value={this.state.password}
|
||||
onChange={this.onChange}
|
||||
endAdornment={
|
||||
|
||||
|
||||
<TextField
|
||||
className={clsx(classes.margin, classes.textField)}
|
||||
label="Password"
|
||||
name="password"
|
||||
id="adornment-password"
|
||||
variant="outlined"
|
||||
margin="normal"
|
||||
type={this.state.showPassword ? 'text' : 'password'}
|
||||
value={this.state.password}
|
||||
onChange={this.onChange}
|
||||
error={!!errors.password}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton aria-label="Toggle password visibility" onClick={this.handleClickShowPassword}>
|
||||
<IconButton
|
||||
edge="end"
|
||||
aria-label="Toggle password visibility"
|
||||
onClick={this.handleClickShowPassword}
|
||||
>
|
||||
{this.state.showPassword ? <Visibility /> : <VisibilityOff />}
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
}
|
||||
autoComplete="current-password"
|
||||
error={!!errors.password}
|
||||
/>
|
||||
</FormControl>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Checkbox value={data.checkNewsletter}
|
||||
<Checkbox
|
||||
id="cypCheckboxNewsletter"
|
||||
value={data.checkNewsletter}
|
||||
onChange={this.handleCheckBoxChange('checkNewsletter')}
|
||||
/>
|
||||
}
|
||||
className={classes.checkbox}
|
||||
label="Je souhaite recevoir la newsletter de Talents Tube"
|
||||
|
||||
|
||||
/>
|
||||
|
||||
<FormControlLabel
|
||||
name="checkCGU"
|
||||
className={!!errors.checkCGU&&classes.checkboxError}
|
||||
className={!!errors.checkCGU ? classes.checkbox + ' ' + classes.checkboxError : classes.checkbox}
|
||||
control={
|
||||
<Checkbox
|
||||
error={!!errors.checkCGU }
|
||||
<Checkbox
|
||||
id="cypCheckboxRegister"
|
||||
value={data.checkCGU}
|
||||
onChange={this.handleCheckBoxChange('checkCGU')}
|
||||
onChange={this.handleCheckBoxChange('checkCGU')} required
|
||||
/>
|
||||
|
||||
|
||||
}
|
||||
label="Je déclare avoir pris connaissance et accepte les termes et les CGU*"
|
||||
label="Je déclare avoir pris connaissance et accepte les termes et les CGU *"
|
||||
required
|
||||
/>
|
||||
<Button type="submit" variant="outlined" color="primary" onClick={this.onRegister}>
|
||||
<Button type="submit" variant="contained" className={classes.button + ' ' + classes.buttonImportant + ' cypButtonRegister'} onClick={this.onRegister}>
|
||||
S'inscrire
|
||||
</Button>
|
||||
|
||||
<Typography
|
||||
paragraph={true}
|
||||
align="center"
|
||||
className={classes.conditionalText}
|
||||
>Ou</Typography>
|
||||
|
||||
</form>
|
||||
<Button variant="outlined" color="primary" onClick={this.props.linkedInAuth}>
|
||||
Connexion avec Linkedin
|
||||
</Button>
|
||||
<Button variant="outlined" color="primary" onClick={this.props.facebookAuth}>
|
||||
Connexion avec Facebook
|
||||
</Button>
|
||||
<div className={classes.form}>
|
||||
|
||||
<SocialButtonsAuth linkedInAuth={this.props.linkedInAuth} facebookAuth={this.props.facebookAuth} />
|
||||
|
||||
<div classes={classes.form}>
|
||||
<Typography
|
||||
paragraph={true}
|
||||
align="center"
|
||||
className={classes.formText}
|
||||
>Je possède un compte Talents Tube ?
|
||||
<Link href={"/Login"} className={classes.link}>
|
||||
Se connecter
|
||||
<Link href={"/Login"} >
|
||||
<span className={classes.formLink}>
|
||||
Se connecter
|
||||
</span>
|
||||
</Link>
|
||||
</Typography>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -226,7 +308,13 @@ class Register extends React.Component {
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
loading: !!state.user.loading,
|
||||
errors: state.user.errors
|
||||
errors: !!state.user.errors ?state.user.errors: {
|
||||
email:null,
|
||||
password:null,
|
||||
firstname:null,
|
||||
lastname:null,
|
||||
checkCGU:null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import React, { Component } from 'react'
|
||||
|
||||
import Link from 'next/link';
|
||||
import React, { Component } from 'react'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Plyr from 'react-plyr';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
|
||||
import VideoVimeo from '../atoms/VideoVimeo'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
@@ -20,21 +23,100 @@ const styles = theme => ({
|
||||
video: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
|
||||
'& .plyr__video-embed': {
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
height: '40vh',
|
||||
maxHeight: '40vh',
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
height: '60vh',
|
||||
},
|
||||
|
||||
},
|
||||
'& .plyr__video-wrapper.plyr__video-embed': {
|
||||
[theme.breakpoints.up('xs')]: {
|
||||
width: '240vw',
|
||||
transform: 'translateX(-55vw)',
|
||||
paddingBottom: '50% !important',
|
||||
},
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
width: 'auto',
|
||||
transform: 'inherit',
|
||||
},
|
||||
},
|
||||
'& .plyr__controls': {
|
||||
background: 'none'
|
||||
},
|
||||
},
|
||||
videoVimeo: {
|
||||
zIndex: '888',
|
||||
height: '65vh',
|
||||
paddingBottom: '0%',
|
||||
overflow: 'hidden',
|
||||
'& iframe': {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 'calc(50% - 125vw)',
|
||||
width: '250vw',
|
||||
height: '120vw',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
height: '48vw',
|
||||
paddingBottom: '0',
|
||||
'& iframe': {
|
||||
position: 'absolute',
|
||||
left: 'calc(50% - 100vw)',
|
||||
width: '200vw',
|
||||
height: '130%',
|
||||
},
|
||||
},
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
height: '48vw',
|
||||
},
|
||||
},
|
||||
text: {
|
||||
zIndex: '999',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
position: 'absolute',
|
||||
bottom: '17vh',
|
||||
left: '3vh',
|
||||
},
|
||||
plyr: {
|
||||
'.plyr--video .plyr__controls': {
|
||||
background: null
|
||||
}
|
||||
}
|
||||
textCompany: {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: 'rgba(30,35,39,0.9)',
|
||||
padding: theme.spacing(0.8, 2),
|
||||
marginBottom: 0,
|
||||
fontWeight: 'bold',
|
||||
fontSize: '1em',
|
||||
},
|
||||
textJob: {
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: 'rgba(30,35,39,0.5)',
|
||||
padding: theme.spacing(0.4, 2),
|
||||
marginBottom: 0,
|
||||
fontWeight: 'lighter',
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
button: {
|
||||
position: 'absolute',
|
||||
bottom: '5vh',
|
||||
left: '3vh',
|
||||
padding: theme.spacing(1, 4),
|
||||
textTransform: 'inherit',
|
||||
zIndex: '999',
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
const videoList = [{ vimeoId: '228345901', title: 'Eurofins', adTitle: 'CDI gestion', link: '/offre?id=22' },
|
||||
{ vimeoId: '230731700', title: 'MULTIPLAST', adTitle: 'CDI example', link: '/offre?id=24' },
|
||||
{ vimeoId: '232953887', title: 'Actual', adTitle: 'Chef général', link: '/offre?id=26' }]
|
||||
|
||||
const videoList = [
|
||||
{ vimeoId: '350927569', title: 'Les Vergers de Châteaubourg', adTitle: 'Conducteurs de ligne (H/F)', link: '/Offre/73-Les-Vergers-de-Châteaubourg-chateaubourg' },
|
||||
{ vimeoId: '350927551', title: 'Grand Ouest Étiquettes', adTitle: 'Conducteurs de machine d’impression', link: '/Offre/79-Grand-Ouest-Étiquettes-Saint-Brieuc' },
|
||||
{ vimeoId: '350927537', title: 'Fym Action', adTitle: 'Promoteur des ventes en alimentaire (H/F)', link: '/Offre/81-Fym-Action-Caudan' }
|
||||
]
|
||||
|
||||
class ShortVideoHooker extends Component {
|
||||
constructor(props) {
|
||||
@@ -54,7 +136,7 @@ class ShortVideoHooker extends Component {
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
this.timer = setInterval(this.tick.bind(this), 500);
|
||||
this.timer = setInterval(this.tick.bind(this), 2000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -62,7 +144,7 @@ class ShortVideoHooker extends Component {
|
||||
}
|
||||
|
||||
tick() {
|
||||
// console.log(this.state)
|
||||
|
||||
if (new Date() - this.state.startDate >= 8000) {
|
||||
|
||||
if (this.state.i == 2)
|
||||
@@ -70,13 +152,13 @@ class ShortVideoHooker extends Component {
|
||||
else
|
||||
this.setState({ i: ++this.state.i, startDate: Date.now() })
|
||||
//Update Plyr
|
||||
if (this.plyrRef) {
|
||||
this.plyrRef.hideControls = true;
|
||||
this.plyrRef.displayDuration = false;
|
||||
this.plyrRef.updateVideoSource(videoList[this.state.i].vimeoId, 'vimeo')
|
||||
this.plyrRef.hideControls = true;
|
||||
this.plyrRef.displayDuration = false;
|
||||
}
|
||||
// if (this.plyrRef) {
|
||||
// this.plyrRef.hideControls = true;
|
||||
// this.plyrRef.displayDuration = false;
|
||||
// this.plyrRef.updateVideoSource(videoList[this.state.i].vimeoId, 'vimeo')
|
||||
// this.plyrRef.hideControls = true;
|
||||
// this.plyrRef.displayDuration = false;
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -84,45 +166,42 @@ class ShortVideoHooker extends Component {
|
||||
render() {
|
||||
let { classes } = this.props;
|
||||
let videoId = videoList[0].vimeoId;
|
||||
// console.log(videoId)
|
||||
|
||||
return (
|
||||
<div className={classes.video}>
|
||||
{/* config at : https://github.com/xDae/react-plyr */}
|
||||
<Plyr
|
||||
//autoplay
|
||||
type="vimeo" // or "vimeo"
|
||||
videoId={videoId}
|
||||
//displayDuration={false}
|
||||
hideControls={true}
|
||||
clickToPlay={false}
|
||||
fullscreen={{ enabled: false, fallback: true, iosNative: false }}
|
||||
disableContextMenu={true}
|
||||
controls={null}
|
||||
ref={this.setPlyrRef}
|
||||
className={classes.plyr}
|
||||
/>
|
||||
<Typography gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.text}>
|
||||
{videoList[this.state.i].title}
|
||||
</Typography>
|
||||
<VideoVimeo classesOverride={ classes.videoVimeo } videoId={videoList[this.state.i].vimeoId} title={videoList[this.state.i].title}
|
||||
options={`?byline=0&title=0&background=1&autoplay=1&mute=1`} />
|
||||
<section className={classes.text}>
|
||||
<Typography gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.textCompany}>
|
||||
{videoList[this.state.i].title}
|
||||
</Typography>
|
||||
|
||||
<Typography gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.text}>
|
||||
{videoList[this.state.i].adTitle}
|
||||
</Typography>
|
||||
<Button variant="outlined">
|
||||
Voir l'offre
|
||||
<Typography gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.textJob}>
|
||||
{videoList[this.state.i].adTitle}
|
||||
</Typography>
|
||||
</section>
|
||||
<Link href={videoList[this.state.i].link}>
|
||||
<Button
|
||||
name="Offre"
|
||||
className={classes.button}
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
>
|
||||
Voir l'offre
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default withStyles(styles)(ShortVideoHooker);
|
||||
export default withStyles(styles)(withWidth()(ShortVideoHooker))
|
||||
@@ -0,0 +1,69 @@
|
||||
import React from 'react';
|
||||
import Link from 'next/link';
|
||||
import clsx from 'clsx';
|
||||
import { loadCSS } from 'fg-loadcss';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import { red } from '@material-ui/core/colors';
|
||||
import Icon from '@material-ui/core/Icon';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
iconHover: {
|
||||
margin: theme.spacing(2),
|
||||
'&:hover': {
|
||||
color: red[800],
|
||||
},
|
||||
},
|
||||
icon: {
|
||||
margin: theme.spacing(1.5),
|
||||
padding: theme.spacing(1.5),
|
||||
borderRadius: '20px',
|
||||
fontSize: '1em',
|
||||
textAlign: 'center',
|
||||
},
|
||||
FacebookIcon: {
|
||||
backgroundColor: theme.palette.social.facebook,
|
||||
},
|
||||
TwitterIcon: {
|
||||
backgroundColor: theme.palette.social.twitter,
|
||||
},
|
||||
LinkedinIcon: {
|
||||
backgroundColor: theme.palette.social.linkedin,
|
||||
},
|
||||
MailIcon: {
|
||||
backgroundColor: theme.palette.social.mail,
|
||||
},
|
||||
}));
|
||||
|
||||
export default function SocialShare(props) {
|
||||
const classes = useStyles();
|
||||
|
||||
React.useEffect(() => {
|
||||
loadCSS(
|
||||
'https://use.fontawesome.com/releases/v5.1.0/css/all.css',
|
||||
document.querySelector('#font-awesome-css'),
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<a href={"https://www.facebook.com/sharer/sharer.php?u=" + encodeURIComponent(props.url)}>
|
||||
<Icon className={clsx(classes.icon, classes.FacebookIcon, 'fab fa-facebook-f')} color="secondary" />
|
||||
</a>
|
||||
<a href={"http://twitter.com/share?url=" + encodeURIComponent(props.url)}>
|
||||
<Icon className={clsx(classes.icon, classes.TwitterIcon, 'fab fa-twitter')} color="secondary" />
|
||||
</a>
|
||||
<a href={"https://www.linkedin.com/shareArticle?mini=true&url=" + encodeURIComponent(props.url + "&title=Talent's Tube")}>
|
||||
<Icon className={clsx(classes.icon, classes.LinkedinIcon, 'fab fa-linkedin-in')} color="secondary" />
|
||||
</a>
|
||||
{/* change because of https://github.com/zeit/next.js/blob/master/errors/invalid-href-passed.md */}
|
||||
<a href={"mailto:?subject=Offre d'emploi Talent's Tube&body=Regardez cette offre : " + props.url} >
|
||||
<Icon className={clsx(classes.icon, classes.MailIcon, 'fas fa-envelope')} color="secondary" />
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -5,79 +5,80 @@ import getConfig from 'next/config'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Avatar from '@material-ui/core/Avatar';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
import CardMedia from '@material-ui/core/CardMedia';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import KeyboardArrowleftIcon from '@material-ui/icons/PlayArrowRounded';
|
||||
import Link from 'next/link';
|
||||
import _ from 'lodash';
|
||||
|
||||
const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
height: '60vh',
|
||||
height: '400px',
|
||||
width: '100%',
|
||||
|
||||
[theme.breakpoints.up('md')]: {
|
||||
width: '30%',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '40%',
|
||||
},
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width: '100%',
|
||||
cursor: 'pointer',
|
||||
transform: 'translateY(-0px)',
|
||||
transition: 'transform 0.25s',
|
||||
'&:hover': {
|
||||
transform: 'translateY(-15px)',
|
||||
transition: 'transform 0.5s, box-shadow 1.2s',
|
||||
'& .cardHover': {
|
||||
backgroundColor: 'rgba(0,0,0,0)',
|
||||
},
|
||||
},
|
||||
|
||||
margin: '2vh 3vh 3vh 1vh',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
margin: '1vh 0 1vh 0',
|
||||
height: '530px',
|
||||
},
|
||||
[theme.breakpoints.up('md')]: {
|
||||
height: '404px',
|
||||
},
|
||||
borderRadius: '0.2em',
|
||||
},
|
||||
details_container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '8vh 0 0 0',
|
||||
},
|
||||
company_title: {
|
||||
fontSize: '1em',
|
||||
fontWeight: 'normal',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
content: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
flexWrap: 'wrap',
|
||||
width: '100%',
|
||||
padding: 0,
|
||||
},
|
||||
cover: {
|
||||
width: '100%',
|
||||
height: '50%',
|
||||
height: '45%',
|
||||
backgroundSize: 'cover',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundBlendMode: 'darken',
|
||||
backgroundColor: 'rgba(30,35,39,0.25)',
|
||||
transition: 'background-color 1s',
|
||||
},
|
||||
logo_container: {
|
||||
logoContainer: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 70,
|
||||
height: 70,
|
||||
width: 80,
|
||||
height: 80,
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
top: '45%',
|
||||
transform: 'translate(-50%, -66%)',
|
||||
backgroundColor: 'white',
|
||||
boxShadow: '0 8px 6px -6px grey',
|
||||
border: 'solid 5px white',
|
||||
borderRadius: '0.5em',
|
||||
backgroundColor: 'white',
|
||||
boxShadow: '0px 6px 15px rgba(30, 35, 39, 0.10)',
|
||||
borderRadius: '5px',
|
||||
},
|
||||
iconPlay: {
|
||||
fontSize: '3em',
|
||||
color: theme.palette.background.grey,
|
||||
position: 'absolute',
|
||||
left: 'calc(50% - 0.5em)',
|
||||
top: 'calc(22.5% - 0.5em)',
|
||||
},
|
||||
logo: {
|
||||
width: '100%',
|
||||
height: 'auto',
|
||||
objectfit: 'contain',
|
||||
borderRadius: 'inherit',
|
||||
},
|
||||
chip: {
|
||||
margin: '0 1vh 0 1vh',
|
||||
@@ -90,28 +91,34 @@ const { publicRuntimeConfig } = getConfig()
|
||||
class Tile extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, rowData, children, tileIndex } = this.props;
|
||||
const imgAdUrl = (rowData.video && rowData.video.thumbnails[0] && rowData.video.thumbnails[0].sizes[4]) ? rowData.video.thumbnails[0].sizes[4].link : publicRuntimeConfig.adListDefaultUrl
|
||||
const imgCompanyUrl = (rowData.presentation_video && rowData.presentation_video.thumbnails[0] && rowData.presentation_video.thumbnails[0].sizes[4]) ? rowData.presentation_video.thumbnails[0].sizes[4].link : publicRuntimeConfig.adListDefaultUrl
|
||||
const imgUrl = (rowData.video) ? imgAdUrl : imgCompanyUrl
|
||||
|
||||
const { classes, rowData, children } = this.props;
|
||||
const logoName = (rowData.company) ? rowData.company.logo : rowData.logo
|
||||
const logoUrl = publicRuntimeConfig.symphonyUrl + publicRuntimeConfig.cachedMediaUrl + logoName
|
||||
|
||||
const imgUrl = (rowData.video.thumbnails[0] && rowData.video.thumbnails[0].sizes[4]) ? rowData.video.thumbnails[0].sizes[4].link : publicRuntimeConfig.adListDefaultUrl
|
||||
const logoUrl = publicRuntimeConfig.logoRootUrl + rowData.company.logo
|
||||
//rowData._embedded.company.business_name
|
||||
|
||||
const router = (rowData.company) ? `/Offre/${rowData.id}-${_.kebabCase(rowData.title)}-${_.kebabCase(rowData.location.gmap_address.name)}` : `/Entreprise/${rowData.id}-${_.kebabCase(rowData.brand_name)}-${_.kebabCase(rowData.addresses.billing.city)}`
|
||||
|
||||
//rowData._embedded.company.brand_name
|
||||
return (
|
||||
<Card className={classes.card} onClick={() => Router.push(`/Offre?id=${rowData.id}`)}>
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={imgUrl}
|
||||
title={rowData.title}
|
||||
/>
|
||||
|
||||
<div className={classes.logo_container}>
|
||||
<Avatar className={classes.logo} src={logoUrl} />
|
||||
</div>
|
||||
{ children }
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Link href={router} >
|
||||
<Card cyp="CardOneOffers" className={classes.card}>
|
||||
<CardContent className={classes.content}>
|
||||
<CardMedia
|
||||
className={classes.cover + ' cardHover'}
|
||||
image={imgUrl}
|
||||
title={rowData.title}
|
||||
/>
|
||||
<KeyboardArrowleftIcon className={classes.iconPlay} />
|
||||
<div className={classes.logoContainer}>
|
||||
<Avatar className={classes.logo} src={logoUrl} alt="company"/>
|
||||
</div>
|
||||
{ children }
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -119,7 +126,7 @@ class Tile extends React.Component {
|
||||
Tile.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
rowData: PropTypes.object.isRequired
|
||||
rowData: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(Tile);
|
||||
@@ -3,69 +3,88 @@ import PropTypes from "prop-types";
|
||||
import Router from 'next/router'
|
||||
import getConfig from 'next/config'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import IconText from './../atoms/IconText';
|
||||
|
||||
const styles = theme => ({
|
||||
details_container: {
|
||||
detailsContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '8vh 0 0 0',
|
||||
justifyContent: 'space-between',
|
||||
position: 'absolute',
|
||||
top: '60%',
|
||||
width: '100%',
|
||||
minHeight: '35%',
|
||||
},
|
||||
company_title: {
|
||||
companyTitle: {
|
||||
fontSize: '1em',
|
||||
fontWeight: 'normal',
|
||||
textTransform: 'uppercase',
|
||||
paddingLeft: theme.spacing(1),
|
||||
paddingRight: theme.spacing(1),
|
||||
minHeight: '8vh',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
minHeight: 'inherit',
|
||||
},
|
||||
},
|
||||
chip: {
|
||||
height: '27px',
|
||||
padding: theme.spacing(1),
|
||||
margin: '0 1vh 0 1vh',
|
||||
},
|
||||
rootIconText: {
|
||||
margin: 0,
|
||||
padding: theme.spacing(0.5, 0),
|
||||
color: theme.palette.secondary.dark,
|
||||
'& p ': {
|
||||
color: theme.palette.secondary.text,
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
class TileCompany extends React.Component {
|
||||
class CompanyOffre extends React.Component {
|
||||
|
||||
render() {
|
||||
|
||||
const { classes, tile } = this.props;
|
||||
|
||||
const offre = (tile.count_ads == 1) ? tile.count_ads + ' offre' : tile.count_ads + ' offres'
|
||||
const nbOffre = (tile.count_ads > 0) ? offre : 'Candidature spontanée'
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.details_container}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.company_title}>
|
||||
{tile.company.business_name}
|
||||
</Typography>
|
||||
<section className={classes.detailsContainer}>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
align="center"
|
||||
className={classes.job_title}>
|
||||
{tile.title}
|
||||
className={classes.companyTitle}>
|
||||
{tile.brand_name ? tile.brand_name : "-"}
|
||||
</Typography>
|
||||
</div>
|
||||
<div>
|
||||
<Chip label={tile.extra.contract_type} className={classes.chip} />
|
||||
<Chip label={tile.location.gmap_address.address_components[0].long_name} className={classes.chip} />
|
||||
</div>
|
||||
|
||||
{tile.addresses && tile.addresses.billing && tile.addresses.billing.city &&
|
||||
<IconText classes={{ root: classes.rootIconText }} variant="location_on" text={tile.addresses.billing.city} />
|
||||
}
|
||||
{tile.title &&
|
||||
<IconText classes={{ root: classes.rootIconText }} variant="business_center" text={tile.title} />
|
||||
}
|
||||
{nbOffre &&
|
||||
<Chip color="primary" label={nbOffre} className={classes.chip} />
|
||||
}
|
||||
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TileCompany.propTypes = {
|
||||
CompanyOffre.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
tile: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(TileCompany);
|
||||
export default withStyles(styles, { withTheme: true })(CompanyOffre);
|
||||
|
||||
@@ -1,31 +1,44 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import Router from 'next/router'
|
||||
import getConfig from 'next/config'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Chip from '@material-ui/core/Chip';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Icon from '@material-ui/core/Icon';
|
||||
import IconText from './../atoms/IconText';
|
||||
|
||||
const styles = theme => ({
|
||||
details_container: {
|
||||
detailsContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
padding: '8vh 0 0 0',
|
||||
justifyContent: 'space-between',
|
||||
position: 'absolute',
|
||||
top: '51%',
|
||||
width: '100%',
|
||||
},
|
||||
company_title: {
|
||||
companyTitle: {
|
||||
fontSize: '0.8em',
|
||||
fontWeight: 'normal',
|
||||
fontWeight: '100',
|
||||
padding: theme.spacing(0, 2),
|
||||
marginTop: '4vh',
|
||||
minHeight: '6vh',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
minHeight: '3vh',
|
||||
},
|
||||
},
|
||||
job_title: {
|
||||
fontSize: '1em',
|
||||
marginLeft: theme.spacing(1),
|
||||
marginRight: theme.spacing(1),
|
||||
jobTitle: {
|
||||
fontSize: '1em',
|
||||
padding: theme.spacing(0, 2),
|
||||
minHeight: '8vh',
|
||||
[theme.breakpoints.up('lg')]: {
|
||||
minHeight: '50px',
|
||||
},
|
||||
},
|
||||
chip: {
|
||||
margin: '0 1vh 0 1vh',
|
||||
rootIconText: {
|
||||
margin: 0,
|
||||
padding: theme.spacing(0.5, 2),
|
||||
color: theme.palette.secondary.dark,
|
||||
justifyContent: 'flex-start',
|
||||
'& p ': {
|
||||
color: theme.palette.secondary.text,
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
@@ -38,31 +51,37 @@ class TileOffre extends React.Component {
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<section className={classes.details_container}>
|
||||
<section className={classes.detailsContainer} >
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h4"
|
||||
align="center"
|
||||
className={classes.company_title}>
|
||||
{tile.company.business_name}
|
||||
className={classes.companyTitle}>
|
||||
{tile.company.brand_name && tile.company.brand_name}
|
||||
</Typography>
|
||||
<Typography
|
||||
gutterBottom
|
||||
variant="headline"
|
||||
variant="h6"
|
||||
component="h3"
|
||||
align="center"
|
||||
className={classes.job_title}>
|
||||
{tile.title}
|
||||
align="left"
|
||||
className={classes.jobTitle}>
|
||||
{tile.title && tile.title}
|
||||
</Typography>
|
||||
</section>
|
||||
<section>
|
||||
<Chip label={tile.extra.contract_type} className={classes.chip} />
|
||||
<Chip label={tile.location.gmap_address.address_components[0].long_name} className={classes.chip} />
|
||||
<IconText />
|
||||
<Icon>add_circle</Icon>
|
||||
|
||||
{/*
|
||||
<IconText classes={{root: classes.rootIconText}} variant="business_center" text={tile.title} />
|
||||
*/}
|
||||
|
||||
|
||||
{tile.location && tile.location.gmap_address && tile.location.gmap_address.address_components && tile.location.gmap_address.address_components[0].long_name &&
|
||||
<IconText classes={{ root: classes.rootIconText }} variant="location_on" text={tile.location.gmap_address.address_components[0].long_name} />
|
||||
}
|
||||
{tile.extra && tile.extra.contract_type &&
|
||||
<IconText classes={{ root: classes.rootIconText }} variant="assignment" text={tile.extra.contract_type} />
|
||||
}
|
||||
</section>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
import Link from 'next/link'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import BottomToolbar from '../organisms/BottomToolbar';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
height: '10vh'
|
||||
},
|
||||
});
|
||||
|
||||
function Footer(props) {
|
||||
const { classes } = props;
|
||||
return (
|
||||
<Fragment>
|
||||
|
||||
</Fragment>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Footer)
|
||||
@@ -1,15 +1,67 @@
|
||||
import Griddle, { utils } from "griddle-react";
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from "react-redux";
|
||||
|
||||
import OffreRow from "../organisms/OffreRow"
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { Button } from '@material-ui/core';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import GriddleCustomTable from "../organisms/GriddleCustomTableComponent";
|
||||
import GriddleCustomNextButton from "../organisms/GriddleCustomNextButton"
|
||||
import GriddleCustomPreviousButton from "../organisms/GriddleCustomPreviousButton"
|
||||
import Loader from './loader'
|
||||
import IconSearch from '@material-ui/icons/Search';
|
||||
import NoResult from './../atoms/NoResult';
|
||||
import _ from "lodash";
|
||||
|
||||
//insights
|
||||
//
|
||||
//https://codesandbox.io/s/pw9zy43v1m?module=%2FGrid.js
|
||||
|
||||
const styles = theme => ({
|
||||
textField: {
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 0,
|
||||
'& input': {
|
||||
fontWeight: '300',
|
||||
color: theme.palette.primary.dark,
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
minWidth: '75vw',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
paddingLeft: theme.spacing(2),
|
||||
[theme.breakpoints.up('md')]: {
|
||||
minWidth: '85vw',
|
||||
},
|
||||
}
|
||||
},
|
||||
griddle: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
'& .griddle-pagination': {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
padding: theme.spacing(2, 0),
|
||||
boxShadown: '0px',
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
},
|
||||
filterForm: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: theme.palette.background.grey,
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
width: '100% !important',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
justifyContent: 'space-evenly',
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const CustomTableBody = ({ rowIds, Row, style, className }) => (
|
||||
<div style={style} className={className}>
|
||||
@@ -24,65 +76,145 @@ class Grid extends Component {
|
||||
success: false,
|
||||
data: {},
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
pageSize: 12,
|
||||
recordCount: 0,
|
||||
filterText: ""
|
||||
promoted: false,
|
||||
filterText: "",
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
|
||||
this.setState({pageSize:this.props.limit})
|
||||
|
||||
this.props.dataFetcher(
|
||||
{
|
||||
offset: this.state.currentPage,
|
||||
limit: this.state.pageSize
|
||||
limit: this.props.limit
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
|
||||
this.setState({ data: nextProps.data });
|
||||
this.setState({ currentPage: nextProps.currentPage });
|
||||
this.setState({ recordCount: nextProps.recordCount });
|
||||
this.setState({ loading: nextProps.loading });
|
||||
this.setState({ limit: nextProps.limit });
|
||||
|
||||
|
||||
const { currentPage, pageSize, filterText, promoted } = this.state;
|
||||
|
||||
if (nextProps.promoted != this.state.promoted) {
|
||||
this.setState({ promoted: nextProps.promoted });
|
||||
this.props.dataFetcher(
|
||||
{
|
||||
offset: this.state.currentPage,
|
||||
limit: this.state.pageSize,
|
||||
keyword: filterText,
|
||||
isPromoted: nextProps.promoted
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
|
||||
_onNext = () => {
|
||||
const { currentPage, pageSize, filterText } = this.state;
|
||||
console.debug(`-- _onNext -- ${0} -- ${1} -- ${2} --`, currentPage, pageSize, filterText)
|
||||
this.props.dataFetcher({ offset: currentPage * pageSize, limit: pageSize, keyword: filterText })
|
||||
const { currentPage, pageSize, filterText, promoted } = this.state;
|
||||
this.props.dataFetcher({ offset: currentPage * pageSize, limit: pageSize, keyword: filterText, isPromoted: promoted })
|
||||
};
|
||||
|
||||
_onPrevious = () => {
|
||||
const { currentPage, pageSize, filterText } = this.state;
|
||||
this.props.dataFetcher({ offset: (currentPage - 2) * pageSize + 1, limit: pageSize, keyword: filterText });
|
||||
const { currentPage, pageSize, filterText, promoted } = this.state;
|
||||
this.props.dataFetcher({ offset: (currentPage - 2) * pageSize + 1, limit: pageSize, keyword: filterText, isPromoted: promoted });
|
||||
};
|
||||
|
||||
_onGetPage = pageNumber => {
|
||||
const { pageSize, filterText } = this.state;
|
||||
this.props.dataFetcher({ offset: (pageNumber - 1) * pageSize + 1, limit: pageSize, keyword: filterText });
|
||||
const { pageSize, filterText, promoted } = this.state;
|
||||
this.props.dataFetcher({ offset: (pageNumber - 1) * pageSize + 1, limit: pageSize, keyword: filterText, isPromoted: promoted });
|
||||
};
|
||||
|
||||
_onFilter = filterText => {
|
||||
this.setState({ currentPage: 1 });
|
||||
const { currentPage, pageSize } = this.state;
|
||||
const { currentPage, pageSize, promoted } = this.state;
|
||||
this.setState({ filterText });
|
||||
this.props.dataFetcher({ offset: (currentPage - 1) * pageSize + 1, limit: pageSize, keyword: filterText });
|
||||
this.props.dataFetcher({ offset: (currentPage - 1) * pageSize + 1, limit: pageSize, keyword: filterText, isPromoted: promoted });
|
||||
};
|
||||
|
||||
searchChange = e => {
|
||||
this.setState({ filterText: e.target.value });
|
||||
}
|
||||
|
||||
searchSubmit = () => {
|
||||
const { currentPage, pageSize, promoted, filterText } = this.state;
|
||||
|
||||
this.props.dataFetcher({ offset: 1, limit: pageSize, keyword: filterText, isPromoted: promoted });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, placeholder } = this.props
|
||||
const style = {
|
||||
styles: {
|
||||
TableBody: { display: "flex", flexWrap: "wrap", justifyContent: "space-around" },
|
||||
TableBody: {
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-around'
|
||||
},
|
||||
Pagination: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingTop: '32px',
|
||||
},
|
||||
NextButton: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '600',
|
||||
fontFamily: 'Dosis',
|
||||
textTransform: 'inherit',
|
||||
marginLeft: '64px',
|
||||
marginRight: '64px',
|
||||
marginTop: '32px',
|
||||
marginBottom: '16px',
|
||||
padding: '9px 16px',
|
||||
borderRadius: '3px',
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid #E8E8E9',
|
||||
color: '#787B7D',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
PreviousButton: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '600',
|
||||
fontFamily: 'Dosis',
|
||||
textTransform: 'inherit',
|
||||
marginLeft: '64px',
|
||||
marginRight: '64px',
|
||||
marginTop: '32px',
|
||||
marginBottom: '16px',
|
||||
padding: '9px 16px',
|
||||
borderRadius: '3px',
|
||||
backgroundColor: 'transparent',
|
||||
border: '1px solid #E8E8E9',
|
||||
color: '#787B7D',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
PageDropdown: {
|
||||
fontSize: '0.9em',
|
||||
fontWeight: '600',
|
||||
fontFamily: 'Dosis',
|
||||
display: 'none',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const { data, currentPage, pageSize, recordCount } = this.state;
|
||||
// console.debug("-- render -- ")
|
||||
// console.debug(data)
|
||||
const { filterText, data, currentPage, pageSize, recordCount, loading } = this.state;
|
||||
|
||||
const NewLayout = ({ Table, Pagination, Filter }) => (
|
||||
<div>
|
||||
<Filter />
|
||||
<Pagination />
|
||||
{/* <Filter /> */}
|
||||
<Table />
|
||||
<Pagination />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -103,39 +235,84 @@ class Grid extends Component {
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<div>
|
||||
<Griddle
|
||||
data={data}
|
||||
pageProperties={{
|
||||
currentPage,
|
||||
pageSize,
|
||||
recordCount
|
||||
}}
|
||||
components={{
|
||||
Layout: NewLayout,
|
||||
Row: this.props.CustomRowComponent,
|
||||
TableContainer: GriddleCustomTable,
|
||||
TableBody: CustomTableBody
|
||||
}}
|
||||
events={{
|
||||
onNext: this._onNext,
|
||||
onPrevious: this._onPrevious,
|
||||
onGetPage: this._onGetPage,
|
||||
onFilter: this._onFilter
|
||||
}}
|
||||
styleConfig={style}
|
||||
/>
|
||||
{this.props.searchable &&
|
||||
<div className={classes.filterForm}>
|
||||
<TextField
|
||||
id="standard-search"
|
||||
disabled={loading}
|
||||
name="search"
|
||||
value={filterText}
|
||||
onChange={this.searchChange}
|
||||
placeholder={placeholder}
|
||||
className={classes.textField}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
margin="dense"
|
||||
variant="filled"
|
||||
onKeyPress={(ev) => {
|
||||
if (ev.key === 'Enter') {
|
||||
this.searchSubmit()
|
||||
}
|
||||
}}
|
||||
type="search"
|
||||
/>
|
||||
<Button onClick={this.searchSubmit}
|
||||
disabled={loading}
|
||||
variant="contained"
|
||||
className={classes.button}>
|
||||
<IconSearch />
|
||||
</Button >
|
||||
</div>
|
||||
}
|
||||
{loading ?
|
||||
(<Loader />)
|
||||
: !_.isEmpty(data) ? (
|
||||
<Griddle
|
||||
className={classes.griddle}
|
||||
data={data}
|
||||
pageProperties={{
|
||||
currentPage,
|
||||
pageSize,
|
||||
recordCount,
|
||||
}}
|
||||
useCustomPagerComponent="true"
|
||||
components={{
|
||||
Layout: NewLayout,
|
||||
Row: this.props.CustomRowComponent,
|
||||
TableContainer: GriddleCustomTable,
|
||||
TableBody: CustomTableBody,
|
||||
PreviousButton: GriddleCustomPreviousButton,
|
||||
NextButton: GriddleCustomNextButton,
|
||||
}}
|
||||
events={{
|
||||
onNext: this._onNext,
|
||||
onPrevious: this._onPrevious,
|
||||
onGetPage: this._onGetPage,
|
||||
onFilter: this._onFilter
|
||||
}}
|
||||
styleConfig={style}
|
||||
/>
|
||||
) : (<NoResult text='Aucun résultat.' />)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Grid.defaultProps = {
|
||||
offset: 1,
|
||||
limit: 12,
|
||||
searchable: true
|
||||
}
|
||||
|
||||
Grid.propTypes = {
|
||||
dataFetcher: PropTypes.func.isRequired,
|
||||
CustomRowComponent: PropTypes.any.isRequired,
|
||||
data: PropTypes.any
|
||||
data: PropTypes.any,
|
||||
searchable: PropTypes.bool
|
||||
}
|
||||
|
||||
export default Grid;
|
||||
export default withStyles(styles)(Grid);
|
||||
|
||||
@@ -1,43 +1,25 @@
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import RootContainer from '../atoms/RootContainer'
|
||||
import Header from '../organisms/Header'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
height: '100%'
|
||||
},
|
||||
head: {
|
||||
width: '100%',
|
||||
|
||||
height: '56px',
|
||||
background: 'silver',
|
||||
height: '64px',
|
||||
background: theme.palette.secondary.main,
|
||||
position: 'fixed',
|
||||
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
height: '64px'
|
||||
}
|
||||
|
||||
zIndex: '1199',
|
||||
},
|
||||
foot: {
|
||||
height: '56px',
|
||||
height: '64px',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
top: '56px',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
height: '64px',
|
||||
top: '64px'
|
||||
},
|
||||
|
||||
top: '64px',
|
||||
},
|
||||
content: {
|
||||
top: '56px',
|
||||
height: 'calc(100vh - 112px)',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
top: '64px',
|
||||
height: 'calc(100vh - 128px)',
|
||||
},
|
||||
top: '64px',
|
||||
height: 'calc(100vh - 64px)',
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
@@ -46,7 +28,7 @@ function Layout(props) {
|
||||
const { classes } = props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<RootContainer>
|
||||
<div id="H" className={classes.head}>
|
||||
<div className={classes.inner}>
|
||||
<Header searchBar={true}/>
|
||||
@@ -56,7 +38,7 @@ function Layout(props) {
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</RootContainer>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Router from 'next/router';
|
||||
import RootContainer from '../atoms/RootContainer'
|
||||
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||
import ArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
const styles = theme => ({
|
||||
inner: {
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
boxShadow: '0 6px 10px 0 rgba(0, 0, 0, 0.04), 0 1px 10px 0 rgba(0, 0, 0, 0.04), 0 3px 5px -1px rgba(0, 0, 0, 0.04)',
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
},
|
||||
head: {
|
||||
zIndex: '999',
|
||||
width: '100%',
|
||||
height: '64px',
|
||||
background: theme.palette.background.main,
|
||||
position: 'fixed',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
height: '64px'
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
fontSize: '2.5em',
|
||||
},
|
||||
title: {
|
||||
color: theme.palette.primary.dark,
|
||||
},
|
||||
content: {
|
||||
top: '64px',
|
||||
width: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'flex-start',
|
||||
top: '64px',
|
||||
position: 'relative',
|
||||
},
|
||||
contentFixed: {
|
||||
height: 'calc(100vh - 64px)',
|
||||
},
|
||||
back: {
|
||||
zIndex: 10,
|
||||
border: 'none',
|
||||
padding: theme.spacing(0.2),
|
||||
textAlign: 'center',
|
||||
textDecoration: 'none',
|
||||
borderRadius: '50%',
|
||||
position: 'absolute',
|
||||
left: theme.spacing(1),
|
||||
},
|
||||
});
|
||||
|
||||
function LayoutBack(props) {
|
||||
|
||||
const { classes, height} = props;
|
||||
|
||||
//height fixed = height 100vh
|
||||
const content = (height == 'fixed') ? classes.content+' '+classes.contentFixed : classes.content
|
||||
return (
|
||||
<RootContainer align='center'>
|
||||
<div id="H" className={classes.head}>
|
||||
<div className={classes.inner}>
|
||||
<ButtonBase name='back'
|
||||
focusRipple
|
||||
className={classes.back}
|
||||
focusVisibleClassName={classes.focusVisible}
|
||||
onClick={() => Router.back()}>
|
||||
<ArrowLeft color="primary" classes={{ root: classes.icon }}></ArrowLeft>
|
||||
</ButtonBase>
|
||||
<Typography
|
||||
component="h1"
|
||||
align="center"
|
||||
className={classes.title}
|
||||
>
|
||||
{props.text}
|
||||
</Typography>
|
||||
</div>
|
||||
</div>
|
||||
<div id="C" className={content}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
</RootContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles)(LayoutBack)
|
||||
@@ -0,0 +1,120 @@
|
||||
import React, { Fragment, useState } from 'react';
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import RootContainer from '../atoms/RootContainer'
|
||||
import Header from '../organisms/Header'
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import IconAdd from '@material-ui/icons/Add';
|
||||
|
||||
import { Button } from '@material-ui/core';
|
||||
|
||||
const styles = theme => ({
|
||||
head: {
|
||||
width: '100%',
|
||||
height: '64px',
|
||||
background: theme.palette.secondary.main,
|
||||
position: 'fixed',
|
||||
zIndex: '999',
|
||||
},
|
||||
foot: {
|
||||
height: '64px',
|
||||
width: '100%',
|
||||
position: 'relative',
|
||||
boxShadow: '0px 0px 10px rgba(30, 35, 39, 0.10)',
|
||||
},
|
||||
content: {
|
||||
top: '64px',
|
||||
height: 'calc(100vh - 64px)',
|
||||
position: 'relative',
|
||||
},
|
||||
filterForm: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: theme.palette.input.main,
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
width: '100% !important',
|
||||
},
|
||||
textField: {
|
||||
textAlign: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: 0,
|
||||
'& input': {
|
||||
fontWeight: '300',
|
||||
color: theme.palette.primary.dark,
|
||||
backgroundColor: theme.palette.input.main,
|
||||
minWidth: '65vw',
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
paddingLeft: theme.spacing(2),
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const chatSubmit = () => {
|
||||
|
||||
}
|
||||
|
||||
function LayoutCandidature(props) {
|
||||
|
||||
const { classes, children } = props;
|
||||
const [text, setText] = useState("");
|
||||
const [displayMessageBar, toggleMessageBar] = useState(false);
|
||||
|
||||
const handleChange = (v) => {
|
||||
toggleMessageBar(v)
|
||||
}
|
||||
const toggleDisplay = (v) => {
|
||||
toggleMessageBar(v)
|
||||
}
|
||||
return (
|
||||
<RootContainer>
|
||||
<div id="H" className={classes.head}>
|
||||
<div className={classes.inner}>
|
||||
<Header searchBar={true} />
|
||||
</div>
|
||||
</div>
|
||||
<div id="C" className={classes.content}>
|
||||
{
|
||||
React.Children.map(props.children, child => {
|
||||
return React.cloneElement(child, { toggleMessageBar: handleChange });
|
||||
})
|
||||
}
|
||||
|
||||
</div>
|
||||
{displayMessageBar &&
|
||||
(<section id="F" className={classes.foot}>
|
||||
<div className={classes.filterForm}>
|
||||
<TextField
|
||||
id="standard-search"
|
||||
name="chattext"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
placeholder={"Message"}
|
||||
className={classes.textField}
|
||||
InputLabelProps={{
|
||||
shrink: true,
|
||||
}}
|
||||
// margin="dense"
|
||||
//variant="filled"
|
||||
onKeyPress={(ev) => {
|
||||
|
||||
if (ev.key === 'Enter') {
|
||||
// Do code here
|
||||
chatSubmit()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button onClick={chatSubmit}
|
||||
variant="contained"
|
||||
className={classes.button}>
|
||||
<IconAdd />
|
||||
</Button >
|
||||
</div>
|
||||
</section>)}
|
||||
</RootContainer>
|
||||
)
|
||||
}
|
||||
|
||||
export default withStyles(styles)(LayoutCandidature)
|
||||
@@ -0,0 +1,129 @@
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from '@material-ui/core';
|
||||
import Router from 'next/router';
|
||||
import RootContainer from '../atoms/RootContainer'
|
||||
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||
import ArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
|
||||
import HideOnScroll from '../atoms/CollapsibleBack';
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
import Header from '../organisms/Header'
|
||||
import {AppBar,CssBaseline,Container,Toolbar,IconButton,Typography} from "@material-ui/core";
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
|
||||
const styles = theme => ({
|
||||
RootContainer: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
maxWidth: 'inherit',
|
||||
},
|
||||
appBar: {
|
||||
backgroundColor: 'transparent',
|
||||
boxShadow: 'none',
|
||||
},
|
||||
foot: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
position: 'fixed',
|
||||
bottom: 0,
|
||||
padding: theme.spacing(1.2, 0),
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
boxShadow: '0px 0px 10px rgba(30, 35, 39, 0.10)',
|
||||
},
|
||||
content: {
|
||||
height: 'calc(100vh - 64px)',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
height: 'calc(100vh - 64px)',
|
||||
},
|
||||
position: 'relative',
|
||||
paddingBottom: '10vh',
|
||||
},
|
||||
back: {
|
||||
alignSelf: 'flex-start',
|
||||
position: 'absolute',
|
||||
zIndex: '999',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.menu.backArrow,
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
padding: theme.spacing(0.2),
|
||||
textAlign: 'center',
|
||||
textDecoration: 'none',
|
||||
display: 'inlineBlock',
|
||||
margin: theme.spacing(2),
|
||||
borderRadius: '50%',
|
||||
},
|
||||
icon: {
|
||||
fontSize: '2.5em',
|
||||
},
|
||||
rootButton: {
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.important + '!important',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
textTransform: 'inherit',
|
||||
marginLeft: theme.spacing(8),
|
||||
marginRight: theme.spacing(8),
|
||||
padding: theme.spacing(1.2, 2),
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.primary.important,
|
||||
},
|
||||
})
|
||||
|
||||
function LayoutCompany(props) {
|
||||
|
||||
const { classes, companyId } = props;
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{isWidthUp('sm', props.width) ?
|
||||
(<Header />)
|
||||
:
|
||||
(<HideOnScroll threshold={50}>
|
||||
<AppBar className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<ButtonBase name='back'
|
||||
focusRipple
|
||||
className={classes.back}
|
||||
focusVisibleClassName={classes.focusVisible}
|
||||
onClick={() => Router.back()}>
|
||||
<ArrowLeft classes={{ root: classes.icon }}></ArrowLeft>
|
||||
</ButtonBase>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</HideOnScroll>)
|
||||
}
|
||||
|
||||
<Container classes={{ root: classes.RootContainer }}>
|
||||
<RootContainer>
|
||||
<div id="C" className={classes.content}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
<Hidden mdUp>
|
||||
<div id="F" className={classes.foot}>
|
||||
<Button
|
||||
variant="contained"
|
||||
className={classes.button}
|
||||
classes={{ root: classes.rootButton }}
|
||||
onClick={() => Router.push(`/Reponse?companyId=${companyId}`)}
|
||||
>
|
||||
Candidature spontanée</Button>
|
||||
</div>
|
||||
</Hidden>
|
||||
|
||||
</RootContainer>
|
||||
</Container>
|
||||
</React.Fragment>
|
||||
|
||||
)
|
||||
}
|
||||
LayoutCompany.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
company: PropTypes.object,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(withWidth()(LayoutCompany))
|
||||
@@ -1,17 +1,15 @@
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Header from '../organisms/Header'
|
||||
import RootContainer from '../atoms/RootContainer'
|
||||
import Footer from '../organisms/Footer';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
height: '100%'
|
||||
},
|
||||
head: {
|
||||
width: '100%',
|
||||
|
||||
height: '56px',
|
||||
background: 'silver',
|
||||
zIndex: '999',
|
||||
height: '64px',
|
||||
background: theme.palette.secondary.main,
|
||||
position: 'fixed',
|
||||
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
@@ -20,16 +18,17 @@ const styles = theme => ({
|
||||
|
||||
},
|
||||
content: {
|
||||
top: '56px',
|
||||
height: 'calc(100vh - 56px)',
|
||||
background: theme.palette.background.grid,
|
||||
top: '64px',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
top: '64px',
|
||||
height: 'calc(100vh - 64px)',
|
||||
},
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
|
||||
}
|
||||
},
|
||||
footer: {
|
||||
marginTop: theme.spacing(10),
|
||||
},
|
||||
});
|
||||
|
||||
function LayoutGrid(props) {
|
||||
@@ -37,25 +36,28 @@ function LayoutGrid(props) {
|
||||
const { classes } = props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<RootContainer>
|
||||
<div id="H" className={classes.head}>
|
||||
<div className={classes.inner}>
|
||||
<Header backLink={props.backLink}/>
|
||||
<Header backLink={props.backLink} />
|
||||
</div>
|
||||
</div>
|
||||
<div id="C" className={classes.content}>
|
||||
{props.children}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div id="F" className={classes.footer}>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
</RootContainer>
|
||||
)
|
||||
}
|
||||
|
||||
LayoutGrid.defaultProps = {
|
||||
backLink:"/"
|
||||
backLink: "/"
|
||||
}
|
||||
LayoutGrid.propTypes = {
|
||||
backLink : PropTypes.string
|
||||
backLink: PropTypes.string
|
||||
}
|
||||
|
||||
export default withStyles(styles)(LayoutGrid)
|
||||
@@ -1,81 +1,115 @@
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Header from '../organisms/Header'
|
||||
import PropTypes from 'prop-types';
|
||||
import { Button } from '@material-ui/core';
|
||||
import Router from 'next/router';
|
||||
import RootContainer from '../atoms/RootContainer'
|
||||
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||
|
||||
import ArrowLeft from '@material-ui/icons/keyboardArrowLeft';
|
||||
|
||||
import ArrowLeft from '@material-ui/icons/KeyboardArrowLeft';
|
||||
import Header from '../organisms/Header'
|
||||
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
height: '100%'
|
||||
},
|
||||
foot: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
|
||||
width: '100%',
|
||||
position: 'fixed',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
height: '64px'
|
||||
},
|
||||
bottom: 0,
|
||||
padding: theme.spacing(1.2, 0),
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
boxShadow: '0px 0px 10px rgba(30, 35, 39, 0.10)',
|
||||
},
|
||||
content: {
|
||||
height: 'calc(100vh - 56px)',
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
height: 'calc(100vh - 64px)',
|
||||
},
|
||||
contentDefault: {
|
||||
height: 'auto',
|
||||
position: 'relative',
|
||||
paddingBottom: '10vh',
|
||||
},
|
||||
contentProvided: {
|
||||
height: '100%',
|
||||
position: 'relative',
|
||||
overflow: 'auto',
|
||||
|
||||
},
|
||||
back:
|
||||
{
|
||||
alignSelf: 'flex-start',
|
||||
position: 'absolute',
|
||||
zIndex:10,
|
||||
color : 'white',
|
||||
backgroundColor: 'grey',
|
||||
zIndex: '999',
|
||||
color: 'white',
|
||||
backgroundColor: theme.palette.menu.backArrow,
|
||||
border: 'none',
|
||||
color: 'white',
|
||||
padding: '10px',
|
||||
padding: theme.spacing(0.2),
|
||||
textAlign: 'center',
|
||||
textDecoration: 'none',
|
||||
display: 'inlineBlock',
|
||||
fontSize: '38px',
|
||||
margin:' 10px 16px',
|
||||
margin: theme.spacing(2),
|
||||
borderRadius: '50%',
|
||||
}
|
||||
},
|
||||
icon: {
|
||||
fontSize: '2.5em',
|
||||
},
|
||||
rootButton: {
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.primary.important + '!important',
|
||||
},
|
||||
},
|
||||
button: {
|
||||
textTransform: 'inherit',
|
||||
marginLeft: theme.spacing(8),
|
||||
marginRight: theme.spacing(8),
|
||||
padding: theme.spacing(1.2, 2),
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.primary.important,
|
||||
},
|
||||
})
|
||||
|
||||
function Layout(props) {
|
||||
function LayoutOffre(props) {
|
||||
|
||||
const { classes } = props;
|
||||
const { classes, offreId, isProvided } = props;
|
||||
const contentClass = (!isProvided) ? classes.contentDefault : classes.contentProvided
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<ButtonBase name='back'
|
||||
focusRipple
|
||||
className={classes.back}
|
||||
focusVisibleClassName={classes.focusVisible}
|
||||
|
||||
onClick={() => Router.back()}>
|
||||
<ArrowLeft></ArrowLeft>
|
||||
</ButtonBase>
|
||||
<div id="C" className={classes.content}>
|
||||
<RootContainer>
|
||||
{isWidthUp('sm', props.width) ?
|
||||
(<Header />)
|
||||
:
|
||||
(<ButtonBase name='back'
|
||||
focusRipple
|
||||
className={classes.back}
|
||||
focusVisibleClassName={classes.focusVisible}
|
||||
onClick={() => Router.back()}>
|
||||
<ArrowLeft classes={{ root: classes.icon }}></ArrowLeft>
|
||||
</ButtonBase>)
|
||||
}
|
||||
<div id="C" className={contentClass}>
|
||||
{props.children}
|
||||
</div>
|
||||
<div id="F" className={classes.foot}>
|
||||
{/* <div className={classes.inner}>
|
||||
<div className={classes.grow} /> */}
|
||||
<Button variant="outlined" color="secondary" >Postuler</Button>
|
||||
{/* <div className={classes.grow} />
|
||||
</div> */}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<Hidden mdUp>
|
||||
{!isProvided && (
|
||||
<div id="F" className={classes.foot}>
|
||||
{/* <div className={classes.inner}>
|
||||
<div className={classes.grow} /> */}
|
||||
<Button
|
||||
variant="contained"
|
||||
className={classes.button + ' cypButtonApply'}
|
||||
classes={{ root: classes.rootButton}}
|
||||
onClick={() => Router.push(`/Reponse?adId=${offreId}`)}
|
||||
>
|
||||
Postuler</Button>
|
||||
{/* <div className={classes.grow} />
|
||||
</div> */}
|
||||
</div>
|
||||
)}
|
||||
</Hidden>
|
||||
|
||||
</RootContainer>
|
||||
)
|
||||
}
|
||||
LayoutOffre.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
offre: PropTypes.object,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Layout)
|
||||
export default withStyles(styles)(withWidth()(LayoutOffre))
|
||||
@@ -1,101 +1,28 @@
|
||||
import React from 'react'
|
||||
import React, { Fragment } from 'react'
|
||||
import CircularProgress from '@material-ui/core/CircularProgress'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
const styles = theme => ({
|
||||
loader: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
fontFamily: 'Dosis',
|
||||
fontWeight: '500',
|
||||
fontSize: '1em',
|
||||
padding: theme.spacing(6,0),
|
||||
color: theme.palette.primary.main,
|
||||
},
|
||||
})
|
||||
|
||||
export default class extends React.Component {
|
||||
class Loader extends React.Component {
|
||||
render() {
|
||||
if (this.props.fullscreen) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<style jsx global>{`
|
||||
.circle-loader {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<div className={classes.loader + ' ' + 'alert alert-info'}><CircularProgress color="primary" />Chargement...</div>
|
||||
)
|
||||
|
||||
.circle-loader .circle {
|
||||
fill: transparent;
|
||||
stroke: rgba(0,0,0,0.2);
|
||||
stroke-width: 4px;
|
||||
animation: dash 2s ease infinite, rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1,95;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -25;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -93;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {transform: rotate(0deg); }
|
||||
100% {transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
<span className="circle-loader">
|
||||
<svg className="circle" width="60" height="60" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="30" cy="30" r="15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<style jsx global>{`
|
||||
.circle-loader {
|
||||
display: block;
|
||||
text-center;
|
||||
padding-left: 50%;
|
||||
}
|
||||
|
||||
.circle-loader .circle {
|
||||
position: relative;
|
||||
left: -30px;
|
||||
fill: transparent;
|
||||
stroke: rgba(0,0,0,0.2);
|
||||
stroke-width: 4px;
|
||||
animation: dash 2s ease infinite, rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1,95;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -25;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -93;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {transform: rotate(0deg); }
|
||||
100% {transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
<span className="circle-loader">
|
||||
<svg className="circle" width="60" height="60" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="30" cy="30" r="15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Loader)
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:8000/",
|
||||
"env":{
|
||||
"nom":"toto",
|
||||
"prenom": "toto",
|
||||
"email":"thomasberrodedeclic42@gmail.com",
|
||||
"password": "123456"
|
||||
},
|
||||
"chromeWebSecurity": false
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,28 @@
|
||||
|
||||
|
||||
describe('RegisterTest', function() {
|
||||
before(function(){
|
||||
|
||||
cy.exec('yarn dropAndCreateDB',{failOnNonZeroExit: false});
|
||||
})
|
||||
it('Test if Register works', function() {
|
||||
cy.viewport(375, 667)
|
||||
cy.visit('/')
|
||||
cy.get('.cypConnexionMenu').click()
|
||||
cy.get('.cypRegister').click({ force: true })
|
||||
cy.url().should('contain', 'Login')
|
||||
cy.get('#nom').type(Cypress.env('nom'))
|
||||
cy.get('#prenom').type(Cypress.env('prenom'))
|
||||
cy.get('#emailRegister').type(Cypress.env('email'))
|
||||
cy.get('#adornment-password').type(Cypress.env('password'))
|
||||
cy.get('#cypCheckboxRegister').check()
|
||||
cy.get('.cypButtonRegister').click().wait(3000)
|
||||
cy.url().should('contain','VerifyEmail')
|
||||
cy.get('span').should('contain',Cypress.env('email'))
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
|
||||
// describe('ConfirmMailTest', function() {
|
||||
// it("Register Form: Email is delievered", function() {
|
||||
// const incoming_mailbox = `thomasberrodedeclic42@gmail.com`;
|
||||
// cy
|
||||
// .task("gmail:check", {
|
||||
// from: "noreply@talentstube.com",
|
||||
// to: incoming_mailbox,
|
||||
// subject: `Bienve nue ${incoming_mailbox} 🔑`
|
||||
// })
|
||||
// .then(email => {
|
||||
// assert.isNotNull(email, `Email was not found`);
|
||||
// });
|
||||
// });
|
||||
// })
|
||||
|
||||
describe("Email assertion:", () => {
|
||||
it("Look for an email with specific subject and link in email body", function() {
|
||||
// debugger; //Uncomment for debugger to work...
|
||||
cy.wait(5000)
|
||||
cy
|
||||
.task("gmail:get-messages", {
|
||||
options: {
|
||||
include_body: true,
|
||||
}
|
||||
})
|
||||
.then(emails => {
|
||||
const found_email = emails.find(email => {
|
||||
return (
|
||||
email.from.indexOf("noreply@talentstube.com") >= 0 &&
|
||||
email.subject.indexOf("Bienvenue thomasberrodedeclic42@gmail.com 🔑") >= 0
|
||||
);
|
||||
});
|
||||
assert.isNotNull(found_email, "Found email👍");
|
||||
const body = found_email.body.text;
|
||||
assert.isTrue(
|
||||
body.indexOf(
|
||||
"http://localhost:8000/ConfirmAccount/"
|
||||
) >= 0,
|
||||
"Found reset link👍"
|
||||
);
|
||||
|
||||
var startUrl = body.indexOf("http://");
|
||||
var endUrl = body.indexOf("*");
|
||||
var link = body.substring(startUrl, endUrl);
|
||||
|
||||
cy.visit(link);
|
||||
|
||||
|
||||
cy.get('p').should('contain','e-mail validé')
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
describe('Visit the whole site as anonymous', function() {
|
||||
it('visit the whole site to see if errors', function() {
|
||||
|
||||
cy.viewport(375, 667)
|
||||
|
||||
//Page d'accueil
|
||||
cy.visit('/')
|
||||
cy.get('p').should('contain', 'à la une')
|
||||
cy.get('.cypConnexionMenu').click()
|
||||
cy.get('span').should('contain', 'Connexion')
|
||||
cy.get('body').click('left')
|
||||
cy.get('button').first().click()
|
||||
cy.get('span').should('contain', 'Accueil')
|
||||
|
||||
//Page de Login
|
||||
cy.visit('/Login')
|
||||
cy.get('.cypButtonConnexion').should('exist')
|
||||
cy.get('.cypLinkedInConnexion').should('exist')
|
||||
|
||||
//Page d'offre(s)
|
||||
cy.visit('/Offres')
|
||||
cy.get('h1').should('contain', 'offres d\'emploi vidéo en cours')
|
||||
cy.get('.cypCardOffre').first().click()
|
||||
cy.get('.cypButtonApply').should('exist')
|
||||
cy.get('p').should('contain','Détails de l\'offre d\'emploi')
|
||||
cy.get('.player').should('exist')
|
||||
|
||||
//Page des entreprises
|
||||
cy.visit('/Entreprises')
|
||||
cy.get('.cypCardCompany').last().click()//could use .eq(2) instead of .last()
|
||||
cy.get('.player').should('exist')
|
||||
|
||||
//Help
|
||||
cy.visit('/Help')
|
||||
cy.get('h1').should('contain','Comment ça marche')
|
||||
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,13 @@
|
||||
describe('Wrong Login Test', function() {
|
||||
it('Teste la connexion d\'un utilisateur', function() {
|
||||
cy.viewport(375, 667)
|
||||
cy.visit('/')
|
||||
cy.get('.cypConnexionMenu').click()
|
||||
cy.get('.cypConnexion').click({ force: true })
|
||||
cy.url().should('contain', 'Login')
|
||||
cy.get('.cypInputEmail input').type('wrongmail@wrong.fr')
|
||||
cy.get('.cypInputPassword input').type('wrongpassword')
|
||||
cy.get('.cypButtonConnexion').click()
|
||||
cy.get('#client-snackbar').should('contain','Mauvais identifiants')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
describe('Apply to an offer', function() {
|
||||
it('Go to an offer and apply to it', function() {
|
||||
|
||||
cy.viewport(375, 667)
|
||||
cy.visit('/')
|
||||
|
||||
cy.get('.cypConnexionMenu').click()
|
||||
cy.get('.cypConnexion').click({ force: true })
|
||||
cy.url().should('contain', 'Login')
|
||||
cy.get('.cypInputEmail input').type(Cypress.env('email'))
|
||||
cy.get('.cypInputPassword input').type(Cypress.env('password'))
|
||||
cy.get('.cypButtonConnexion').click().wait(3000)
|
||||
|
||||
|
||||
cy.get('.cypButtonOffers').click().wait(3000)
|
||||
cy.url().should('contain', 'Offres')
|
||||
cy.get('.cypCardOffre').first().click().wait(3000)
|
||||
cy.url().should('contain', 'Offre')
|
||||
cy.get('.cypButtonApply').first().click().wait(3000)
|
||||
cy.url().should('contain', 'Reponse')
|
||||
|
||||
cy.fixture('cypVid.mov', 'base64').then(fileContent => {
|
||||
cy.get('.cypButtonImportVid').invoke('show').upload(
|
||||
{ fileContent, fileName: 'cypVid.mov', mimeType: 'video/quicktime', encoding: 'base64' },
|
||||
{ subjectType: 'input', force: true }
|
||||
)}).wait(3000)
|
||||
cy.get('.cypButtonSendApplication').click().wait(3000)
|
||||
})
|
||||
|
||||
//Should verify if application is received
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
describe('ConnexionTest', function() {
|
||||
it('Test login', function() {
|
||||
cy.viewport(375, 667)
|
||||
cy.visit('/')
|
||||
cy.get('.cypConnexionMenu').click()
|
||||
cy.get('.cypConnexion').click({ force: true })
|
||||
cy.url().should('contain', 'Login')
|
||||
cy.get('.cypInputEmail input').type(Cypress.env('email'))
|
||||
cy.get('.cypInputPassword input').type(Cypress.env('password'))
|
||||
cy.get('.cypButtonConnexion').click().wait(3000)
|
||||
cy.get('.cypConnexionMenu').click()
|
||||
cy.get('h3').should('contain', Cypress.env('email') )
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
describe('VisitAsUser', function() {
|
||||
it('Test if user can access to his pages', function() {
|
||||
cy.viewport(375, 667)
|
||||
cy.visit('/')
|
||||
cy.get('.cypConnexionMenu').click()
|
||||
cy.get('.cypConnexion').click({ force: true })
|
||||
cy.url().should('contain', 'Login')
|
||||
cy.get('.cypInputEmail input').type(Cypress.env('email'))
|
||||
cy.get('.cypInputPassword input').type(Cypress.env('password'))
|
||||
cy.get('.cypButtonConnexion').click().wait(3000)
|
||||
|
||||
cy.visit('/Candidatures')
|
||||
|
||||
|
||||
// cy.get('h1').should('contain', Cypress.env('Mes candidatures') )
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,17 @@
|
||||
describe('test_name', function() {
|
||||
|
||||
it('what_it_does', function() {
|
||||
|
||||
cy.viewport(1536, 674)
|
||||
|
||||
cy.visit('http://localhost:8000/')
|
||||
|
||||
cy.get('.MuiList-root > div > .jss56:nth-child(1) > .MuiListItemText-root > .MuiTypography-root').click()
|
||||
|
||||
cy.get('.MuiList-root > div > .jss56:nth-child(2) > .MuiListItemText-root > .MuiTypography-root').click()
|
||||
|
||||
cy.get('.MuiList-root > div > .jss56:nth-child(3) > .MuiListItemText-root > .MuiTypography-root').click()
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
@@ -0,0 +1 @@
|
||||
{"installed":{"client_id":"438657734125-poo68tqp3dosq2od8udgipdqietbav0d.apps.googleusercontent.com","project_id":"tt-app-251309","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_secret":"m_zcFplh1LZlMHl6bwkBBFl8","redirect_uris":["http://localhost"],"javascript_origins":["http://localhost"]}}
|
||||
@@ -0,0 +1 @@
|
||||
{"access_token":"ya29.Glt0B7O-MEh7BOQ6yJyZY1628D2mH1lbVz7LaJfiUgKJJv5mt1DljeQsZU49z-5F9GYsWVn0E5ylYj2yWuGLGb2J0sZJjVUWMLYn333dRbk-0469E08Hmc3GkIOO","refresh_token":"1/42U4Ny2XVLbf3D3xO4S2bwyyX183RuqQ2kcBmND7hpr5japueYf6psdLEWzIFfCx","scope":"https://www.googleapis.com/auth/gmail.readonly","token_type":"Bearer","expiry_date":1567156006741}
|
||||
@@ -0,0 +1,52 @@
|
||||
// ***********************************************************
|
||||
// This example plugins/index.js can be used to load plugins
|
||||
//
|
||||
// You can change the location of this file or turn off loading
|
||||
// the plugins file with the 'pluginsFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/plugins-guide
|
||||
// ***********************************************************
|
||||
|
||||
// This function is called when a project is opened or re-opened (e.g. due to
|
||||
// the project's config changing)
|
||||
|
||||
const gmail = require("gmail-tester");
|
||||
const path = require("path");
|
||||
module.exports = (on, config) => {
|
||||
// `on` is used to hook into various events Cypress emits
|
||||
// `config` is the resolved Cypress config
|
||||
on("before:browser:launch", (browser = {}, args) => {
|
||||
if (browser.name === "chrome") {
|
||||
args.push("--remote-debugging-port=9221");
|
||||
return args;
|
||||
}
|
||||
});
|
||||
|
||||
// on("task", {
|
||||
// "gmail:check": async args => {
|
||||
// const { from, to, subject } = args;
|
||||
// const email = await gmail.check_inbox(
|
||||
// path.resolve(__dirname, "credentials.json"), // credentials.json is inside plugins/ directory.
|
||||
// path.resolve(__dirname, "gmail_token.json"), // gmail_token.json is inside plugins/ directory.
|
||||
// subject,
|
||||
// from,
|
||||
// to,
|
||||
// 10, // Poll interval (in seconds)
|
||||
// 30 // Maximum poll interval (in seconds). If reached, return null, indicating the completion of the task().
|
||||
// );
|
||||
// return email;
|
||||
// }
|
||||
// });
|
||||
|
||||
on("task", {
|
||||
"gmail:get-messages": async args => {
|
||||
const messages = await gmail.get_messages(
|
||||
path.resolve(__dirname, "credentials.json"),
|
||||
path.resolve(__dirname, "gmail_token.json"),
|
||||
args.options
|
||||
);
|
||||
return messages;
|
||||
}
|
||||
});
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
@@ -0,0 +1,46 @@
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
// existing commands.
|
||||
//
|
||||
// For more comprehensive examples of custom
|
||||
// commands please read more here:
|
||||
// https://on.cypress.io/custom-commands
|
||||
// ***********************************************
|
||||
//
|
||||
//
|
||||
// -- This is a parent command --
|
||||
// Cypress.Commands.add("login", (email, password) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a child command --
|
||||
// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is a dual command --
|
||||
// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||
//
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
import 'cypress-file-upload';
|
||||
|
||||
Cypress.Commands.add('login', () => {
|
||||
cy.viewport(375, 667)
|
||||
cy.visit('/')
|
||||
cy.request({
|
||||
method: 'POST',
|
||||
url: 'http://localhost/api/login_check',
|
||||
body: {
|
||||
|
||||
username: 'cypress.tt.edeclic@gmail.com',
|
||||
password: 'qJxY4kovD13W'
|
||||
|
||||
}
|
||||
})
|
||||
.then((resp) => {
|
||||
window.localStorage.setItem('ttJWT', resp.body.token)
|
||||
})
|
||||
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
// ***********************************************************
|
||||
// This example support/index.js is processed and
|
||||
// loaded automatically before your test files.
|
||||
//
|
||||
// This is a great place to put global configuration and
|
||||
// behavior that modifies Cypress.
|
||||
//
|
||||
// You can change the location of this file or turn off
|
||||
// automatically serving support files with the
|
||||
// 'supportFile' configuration option.
|
||||
//
|
||||
// You can read more here:
|
||||
// https://on.cypress.io/configuration
|
||||
// ***********************************************************
|
||||
|
||||
// Import commands.js using ES2015 syntax:
|
||||
import './commands'
|
||||
|
||||
// Alternatively you can use CommonJS syntax:
|
||||
// require('./commands')
|
||||
|
||||
// before(() => {
|
||||
// cy.exec('yarn dropAndCreateDB');
|
||||
// })
|
||||
|
||||
Cypress.on('uncaught:exception', (err, runnable) => {
|
||||
// returning false here prevents Cypress from
|
||||
// failing the test
|
||||
return false
|
||||
})
|
||||
|
||||
Binary file not shown.
+115
-24
@@ -1,47 +1,138 @@
|
||||
import axios from "axios";
|
||||
|
||||
import { fetchAnswerFailure } from "../actions/answer";
|
||||
import setAuthorizationHeader from "./setAuthorizationHeader";
|
||||
import Router from 'next/router';
|
||||
//axios.defaults.headers.common = { 'X-Requested-With': 'XMLHttpRequest' }
|
||||
axios.defaults.baseURL = (process.env.NODE_ENV !== 'production') ? 'http://localhost/app_dev.php/' : ''
|
||||
axios.defaults.baseURL = (process.env.NODE_ENV !== 'production') ?
|
||||
'http://localhost/app_dev.php/' :
|
||||
process.env.SERVER_URL;
|
||||
|
||||
axios.interceptors.response.use(response => {
|
||||
return response;
|
||||
}, error => {
|
||||
if (error.response.status === 401) {
|
||||
localStorage.setItem('ttJWT', '');
|
||||
Router.push("/Login");
|
||||
}
|
||||
return error;
|
||||
});
|
||||
|
||||
export default {
|
||||
user: {
|
||||
login: credentials =>
|
||||
axios.post("/api/login_check", { username: credentials.email, password: credentials.password }).then(res => res.data),
|
||||
loginOauth: ({token,type}) =>
|
||||
axios.post("/api/login_check_oauth", {'oauth-token':token,'oauth-type':type}),
|
||||
fetchUser: () =>{
|
||||
console.log("axios /api/user")
|
||||
return axios.get("/api/user").then(res => {console.log(res); return res.data})},
|
||||
subscribeToNewsletter: (data) =>
|
||||
{
|
||||
console.log(data)
|
||||
return axios.post("http://localhost:3000/NewsletterSubscription", data).then(res => {console.log(res); return res})
|
||||
loginOauth: ({ token, type }) =>
|
||||
axios.post("/api/login_check_oauth", { 'oauth-token': token, 'oauth-type': type }),
|
||||
fetchUser: () => {
|
||||
return axios.get("/api/user").then(res => { return res.data })
|
||||
},
|
||||
register: (data) => {
|
||||
console.log(data)
|
||||
return axios.post("/api/register", { data}).then(res => res)
|
||||
subscribeToNewsletter: (data) => {
|
||||
return axios.post(process.env.FRONT_SERVER + "/NewsletterSubscription", data).then(res => { return res })
|
||||
},
|
||||
register: (payload) => {
|
||||
return axios.post("/api/register", payload).then(res => res)
|
||||
},
|
||||
sendconfirmationEmail: (data) => {
|
||||
return axios.post(process.env.FRONT_SERVER + "/sendConfirmationEmail", data).then(res => { return res })
|
||||
},
|
||||
confirmUserAccount: (payload) => {
|
||||
return axios.post("/api/confirmAccount", payload).then(res => res)
|
||||
},
|
||||
updateUser: (payload) => {
|
||||
return axios.post("/api/update_user", payload, {
|
||||
headers: {
|
||||
'Content-Type': `multipart/form-data;`
|
||||
},
|
||||
withCredentials: true
|
||||
}).then(res => res)
|
||||
},
|
||||
resetPassword: (payload) => {
|
||||
return axios.post("/api/reset_password", payload).then(res => res)
|
||||
},
|
||||
subscribeUserToPush: (payload) => {
|
||||
setAuthorizationHeader(localStorage.ttJWT)
|
||||
return axios.post("/api/userSubscriptionToPush", payload).then(res => res)
|
||||
},
|
||||
fetchUserSubscriptionToPush: () => {
|
||||
setAuthorizationHeader(localStorage.ttJWT)
|
||||
return axios.get(`/api/userSubscriptionToPush`).then(res => res)
|
||||
},
|
||||
uploadPicture: (payload) => {
|
||||
return axios.post("/api/update_profile_picture", payload, {
|
||||
headers: {
|
||||
'Content-Type': `multipart/form-data;`
|
||||
},
|
||||
withCredentials: true
|
||||
}).then(res => res)
|
||||
},
|
||||
},
|
||||
ads: {
|
||||
fetchAd: (id) => {
|
||||
console.debug("--- fetchAd")
|
||||
console.debug(id)
|
||||
return axios.get(`api/ads/${id}`).then(res => res.data)
|
||||
},
|
||||
|
||||
|
||||
//offset is the index position of the first element to start requesting at
|
||||
// use 0|1 for isPromoted
|
||||
fetchAds: ({ limit = null, order = "", keyword = "", offset = 1, status='PUBLISHED', isPromoted=null}) => {
|
||||
console.debug("--- fetchAds" + offset, limit, order, keyword)
|
||||
return axios.get('api/ads', { params: { offset, limit, order, keyword, status, isPromoted } })
|
||||
fetchAds: ({ limit = null, order = "", keyword = "", offset = 1, status = 'PUBLISHED', isPromoted = null, isEmphasis = null }) => {
|
||||
isPromoted = isPromoted !== null ? isPromoted * 1 : isPromoted;
|
||||
isEmphasis = isEmphasis !== null ? isEmphasis * 1 : isEmphasis;
|
||||
|
||||
return axios.get('api/ads', { params: { offset, limit, order, keyword, status, isPromoted, isEmphasis } })
|
||||
.then(res => res.data)
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
companies: {
|
||||
fetchCompany: id =>
|
||||
axios.get(`api/companies/${id}`).then(res => res.data),
|
||||
fetchCompanies: (limit = "", order = "", keyword = "", offset = 1) =>
|
||||
axios.get('api/companies', { params: { offset, limit, order, keyword } }).then(res => res.data)
|
||||
|
||||
fetchCompanies: ({ limit = null, order = "", keyword = "", offset = 1, isEmphasis = null }) => {
|
||||
|
||||
isEmphasis = isEmphasis !== null ? isEmphasis * 1 : isEmphasis;
|
||||
return axios.get('api/companies', { params: { offset, limit, order, keyword, isEmphasis } }).then(res => res.data);
|
||||
}
|
||||
},
|
||||
answers: {
|
||||
postAnswer: (payload) => {
|
||||
return axios.post("/api/answers", { adId: payload.adId, mediaId: payload.mediaId, companyId: payload.companyId }).then(res => res)
|
||||
},
|
||||
fetchAdsAnswered: ({ limit = null, order = "", keyword = "", offset = 1 }) => {
|
||||
return axios.get(`api/answers`, { params: { offset, limit, order, keyword } }).then(res => res.data)
|
||||
},
|
||||
fetchAnswer: (payload) => {
|
||||
setAuthorizationHeader(localStorage.ttJWT)
|
||||
return axios.get(`api/answers/${payload}`).then(res => res.data)
|
||||
},
|
||||
postMessage: (payload) => {
|
||||
return axios.post("/api/answers/message", { answerId: payload.answerId, message: payload.message }).then(res => res)
|
||||
},
|
||||
fetchAnswerMessages: (payload) => {
|
||||
setAuthorizationHeader(localStorage.ttJWT)
|
||||
return axios.get(`api/answers/${payload}/messages`).then(res => res.data)
|
||||
},
|
||||
},
|
||||
medias: {
|
||||
uploadMedia: (payload) => {
|
||||
return axios.post("/api/media", payload, {
|
||||
headers: {
|
||||
'Content-Type': `multipart/form-data;`
|
||||
},
|
||||
withCredentials: true
|
||||
}).then(res => res)
|
||||
},
|
||||
},
|
||||
stats: {
|
||||
countAdView: id => {
|
||||
if (!!localStorage.ttJWT)
|
||||
setAuthorizationHeader(localStorage.ttJWT)
|
||||
axios.get(`api/adAjaxRecStat/${id}`)
|
||||
},
|
||||
countCompanyView: id => {
|
||||
if (!!localStorage.ttJWT)
|
||||
setAuthorizationHeader(localStorage.ttJWT)
|
||||
axios.get(`api/companyAjaxRecStat/${id}`)
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -215,7 +215,7 @@ module.exports = () => {
|
||||
}
|
||||
})
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('Generated sign in link ' + url + ' for ' + email)
|
||||
console.info('Generated sign in link ' + url + ' for ' + email)
|
||||
}
|
||||
},
|
||||
// Credentials Sign In
|
||||
|
||||
@@ -26,7 +26,6 @@ require('dotenv').load()
|
||||
module.exports = () => {
|
||||
let providers = []
|
||||
|
||||
if (process.env.FACEBOOK_ID && process.env.FACEBOOK_SECRET) {
|
||||
providers.push({
|
||||
providerName: 'Facebook',
|
||||
providerOptions: {
|
||||
@@ -34,8 +33,8 @@ module.exports = () => {
|
||||
},
|
||||
Strategy: require('passport-facebook').Strategy,
|
||||
strategyOptions: {
|
||||
clientID: process.env.FACEBOOK_ID,
|
||||
clientSecret: process.env.FACEBOOK_SECRET,
|
||||
clientID: process.env.NODE_ENV === 'production' ? process.env.TT_FACEBOOK_ID : process.env.FACEBOOK_ID,
|
||||
clientSecret: process.env.NODE_ENV === 'production' ? process.env.TT_FACEBOOK_SECRET : process.env.FACEBOOK_SECRET,
|
||||
profileFields: ['id', 'displayName', 'email', 'link']
|
||||
},
|
||||
getProfile(profile) {
|
||||
@@ -47,9 +46,8 @@ module.exports = () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
if (process.env.LINKEDIN_ID && process.env.LINKEDIN_SECRET) {
|
||||
providers.push({
|
||||
providerName: 'Linkedin',
|
||||
providerOptions: {
|
||||
@@ -57,12 +55,11 @@ module.exports = () => {
|
||||
},
|
||||
Strategy: require('@sokratis/passport-linkedin-oauth2').Strategy,
|
||||
strategyOptions: {
|
||||
clientID: process.env.LINKEDIN_ID,
|
||||
clientSecret: process.env.LINKEDIN_SECRET,
|
||||
clientID: process.env.NODE_ENV === 'production' ? process.env.TT_LINKEDIN_ID : process.env.LINKEDIN_ID,
|
||||
clientSecret: process.env.NODE_ENV === 'production' ? process.env.TT_LINKEDIN_SECRET : process.env.LINKEDIN_SECRET,
|
||||
profileFields: ['r_emailaddress', 'r_basicprofile']
|
||||
},
|
||||
getProfile(profile) {
|
||||
console.log(profile);
|
||||
// Normalize profile into one with {id, name, email} keys
|
||||
return {
|
||||
id: profile.id,
|
||||
@@ -71,7 +68,7 @@ module.exports = () => {
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return providers
|
||||
}
|
||||
+42
-45
@@ -4,47 +4,53 @@ const optimizedImages = require('next-optimized-images')
|
||||
const webpack = require("webpack")
|
||||
const withCSS = require('@ostai/next-css')
|
||||
const withOffline = require('next-offline')
|
||||
const Dotenv = require('dotenv-webpack');
|
||||
|
||||
const { ANALYZE, DEV } = process.env
|
||||
|
||||
module.exports = withPlugins([
|
||||
[withCSS],
|
||||
[optimizedImages, {
|
||||
/* config for next-optimized-images */
|
||||
}],
|
||||
[withOffline, {
|
||||
dontAutoRegisterSw: DEV ? true : false,
|
||||
workboxOpts: {
|
||||
globPatterns: ['static/**/*'],
|
||||
globDirectory: '.',
|
||||
module.exports = withPlugins(
|
||||
[
|
||||
[withCSS],
|
||||
[optimizedImages, {
|
||||
/* config for next-optimized-images */
|
||||
}],
|
||||
[withOffline, {
|
||||
dontAutoRegisterSw: DEV ? true : false,
|
||||
workboxOpts: {
|
||||
runtimeCaching: [
|
||||
// { urlPattern: /.*auth.*/, handler: 'networkOnly' },
|
||||
// { urlPattern: /.*account.*/, handler: 'networkOnly' },
|
||||
{ urlPattern: /^https?.*/, handler: 'networkFirst' },
|
||||
{ urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/,
|
||||
handler: "cacheFirst",
|
||||
options: {
|
||||
cacheName: "images",
|
||||
expiration: {
|
||||
maxEntries: 100
|
||||
}
|
||||
}}
|
||||
// { urlPattern: /.*auth.*/, handler: 'networkOnly' },
|
||||
// { urlPattern: /.*account.*/, handler: 'networkOnly' },
|
||||
{ urlPattern: /^https?.*/, handler: 'NetworkFirst' },
|
||||
{
|
||||
urlPattern: /\.(?:png|jpg|jpeg|svg|webp)$/,
|
||||
handler: "CacheFirst",
|
||||
options: {
|
||||
cacheName: "images",
|
||||
expiration: {
|
||||
maxEntries: 100
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
importScripts:
|
||||
['/static/js/firebase-messaging-sw.js',
|
||||
'/static/js/backgroundSync-sw.js'
|
||||
]
|
||||
},
|
||||
|
||||
}],
|
||||
|
||||
[{
|
||||
publicRuntimeConfig: {
|
||||
|
||||
adListDefaultUrl: "https://www.talentstube.com/img/default_picture_company.svg",
|
||||
logoRootUrl: 'https://www.talentstube.com/media/cache/profile_thumb/uploads/img/'
|
||||
}
|
||||
}]],
|
||||
['/static/js/Edeclic-service-worker.js',
|
||||
'/static/js/backgroundSync-sw.js'
|
||||
]
|
||||
},
|
||||
}],
|
||||
[{
|
||||
publicRuntimeConfig: {
|
||||
symphonyUrl: 'https://www.talentstube.com',
|
||||
frontServerUrl: 'https://pp.app.talentstube.com',
|
||||
adListDefaultUrl: '/static/images/default/default_picture_company.svg',
|
||||
cachedMediaUrl: '/media/cache/profile_thumb/uploads/img/',
|
||||
postedContentUrl: '/uploads/img/',
|
||||
ReCAPTCHA: {
|
||||
"siteKey": "6LdyqLwUAAAAADKI5uC7d5W9VqVgzd8VEmMU2Yi-"
|
||||
},
|
||||
}
|
||||
}]
|
||||
],
|
||||
{
|
||||
webpack: (config, { isServer }) => {
|
||||
|
||||
@@ -68,20 +74,11 @@ module.exports = withPlugins([
|
||||
RecordRTC: "recordrtc"
|
||||
};
|
||||
|
||||
|
||||
config.resolve.alias = { ...config.resolve.alias, ...videojsAlias };
|
||||
config.plugins.push(videojsPlugin);
|
||||
|
||||
// config.module.rules.push(
|
||||
// {
|
||||
// test: /\.css$/,
|
||||
// loader: 'style-loader!css-loader',
|
||||
// include: [/node_modules/]
|
||||
// },
|
||||
config.plugins.push(new Dotenv());
|
||||
|
||||
// )
|
||||
|
||||
console.log(config);
|
||||
return config
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# crawl setup
|
||||
|
||||
User-agent: *
|
||||
Disallow: /Login*
|
||||
|
||||
# Sitemap
|
||||
|
||||
Sitemap: https://app.talentstube.com/sitemap_local.xml
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
<url>
|
||||
<loc>https://app.talentstube.com/Candidature</loc>
|
||||
<lastmod>2019-09-17</lastmod>
|
||||
</url>,<url>
|
||||
<loc>https://app.talentstube.com/Cgu</loc>
|
||||
<lastmod>2019-09-17</lastmod>
|
||||
</url>,<url>
|
||||
<loc>https://app.talentstube.com/Confidentialite</loc>
|
||||
<lastmod>2019-11-04</lastmod>
|
||||
</url>,<url>
|
||||
<loc>https://app.talentstube.com/Entreprises</loc>
|
||||
<lastmod>2019-09-17</lastmod>
|
||||
</url>,<url>
|
||||
<loc>https://app.talentstube.com/Help</loc>
|
||||
<lastmod>2019-10-11</lastmod>
|
||||
</url>,<url>
|
||||
<loc>https://app.talentstube.com/Login</loc>
|
||||
<lastmod>2019-10-14</lastmod>
|
||||
</url>,<url>
|
||||
<loc>https://app.talentstube.com/MentionsLegales</loc>
|
||||
<lastmod>2019-11-04</lastmod>
|
||||
</url>,<url>
|
||||
<loc>https://app.talentstube.com/Offres</loc>
|
||||
<lastmod>2019-10-16</lastmod>
|
||||
</url>
|
||||
</urlset>
|
||||
+34
-12
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "nextjs",
|
||||
"name": "talentstube",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"resolutions": {
|
||||
@@ -7,22 +7,26 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@blunck/next-alias": "^1.0.0",
|
||||
"@material-ui/core": "^4.0.0",
|
||||
"@material-ui/core": "^4.2.0",
|
||||
"@material-ui/icons": "^3.0.1",
|
||||
"@ostai/next-css": "^1.0.2",
|
||||
"@sokratis/passport-linkedin-oauth2": "^2.1.0",
|
||||
"axios": "^0.18.0",
|
||||
"connect-mongo": "^2.0.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^3.2.0",
|
||||
"cypress": "^3.4.1",
|
||||
"cypress-file-upload": "^3.3.2",
|
||||
"dotenv": "^8.0.0",
|
||||
"dotenv-webpack": "^1.7.0",
|
||||
"eslint": "^5.6.0",
|
||||
"express": "^4.16.3",
|
||||
"fg-loadcss": "^2.1.0",
|
||||
"griddle-react": "^1.13.1",
|
||||
"jss": "latest",
|
||||
"lodash": "^4.17.11",
|
||||
"mongodb": "^3.1.6",
|
||||
"nedb": "^1.8.0",
|
||||
"next": "8.1.0",
|
||||
"next": "^9.1.1",
|
||||
"next-auth": "^1.11.0",
|
||||
"next-compose-plugins": "^2.1.1",
|
||||
"next-offline": "^4.0.2",
|
||||
@@ -36,34 +40,52 @@
|
||||
"nodemailer-smtp-transport": "^2.7.4",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-linkedin-oauth2": "https://github.com/Huaoe/passport-linkedin-oauth2.git",
|
||||
"passport-oauth2": "https://github.com/Huaoe/passport-oauth2.git",
|
||||
"prop-types": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react": "^16.9.0",
|
||||
"react-dom": "^16.9.0",
|
||||
"react-ga": "^2.5.7",
|
||||
"react-google-recaptcha": "^2.0.1",
|
||||
"react-jss": "latest",
|
||||
"react-plyr": "^2.1.1",
|
||||
"react-redux": "^7.0.3",
|
||||
"react-swipeable-views": "^0.13.3",
|
||||
"reactstrap": "^6.4.0",
|
||||
"recordrtc": "^5.5.4",
|
||||
"redux": "^4.0.1",
|
||||
"redux-action-buffer": "^1.2.0",
|
||||
"redux-devtools-extension": "^2.13.8",
|
||||
"redux-persist": "^5.10.0",
|
||||
"redux-saga": "^1.0.2",
|
||||
"reselect": "^4.0.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"style-loader": "^1.0.0",
|
||||
"terser": "3.14",
|
||||
"universal-cookie": "^3.0.4",
|
||||
"validator": "^11.0.0",
|
||||
"video.js": "latest",
|
||||
"videojs-record": "latest",
|
||||
"videojs-record": "^3.8.0",
|
||||
"webrtc-adapter": "^7.2.3"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env PORT=3000 node server.js",
|
||||
"build": "next build",
|
||||
"start": "cross-env PORT=8000 NODE_ENV=production node server.js"
|
||||
"build": "next build && yarn postExport && yarn copysw && yarn copySiteMap",
|
||||
"start": "cross-env PORT=8000 NODE_ENV=production node server.js",
|
||||
"startPreprod": "cross-env PORT=8000 NODE_ENV=preproduction node server.js",
|
||||
"pm2-prod": "yarn build && pm2 start --name 'next' -- start",
|
||||
"bs": "yarn build && yarn start",
|
||||
"test": "yarn cypress run",
|
||||
"dropAndCreateDB": "yarn dropDB && yarn createDB && yarn restoredb",
|
||||
"terminateDB": "docker exec -i tt-website.postgres.dev psql --dbname database -U postgres -c \"select pg_terminate_backend(pid) from pg_stat_activity where datname='database';\"",
|
||||
"dropDB": "docker exec -i tt-website.postgres.dev psql --dbname template1 -U postgres -c \"DROP DATABASE database;\"",
|
||||
"createDB": "docker exec -i tt-website.postgres.dev psql --dbname template1 -U postgres -c \"CREATE DATABASE database;\"",
|
||||
"restoredb": "cat cypress/postgres.sql | docker exec -i tt-website.postgres.dev psql --dbname database -U user",
|
||||
"restoreuser": "docker exec -i tt-website.postgres.dev psql --dbname database -U postgres -c \"DELETE FROM fos_user WHERE email = 'thomasberrodedeclic42@gmail.com'\"",
|
||||
"backupdb": "docker exec -t tt-website.postgres.dev pg_dumpall --database database -c -U postgres > cypress/postgres.sql",
|
||||
"copySiteMap": "cp out/* .next/",
|
||||
"postExport": "node src/postExport.js",
|
||||
"copysw": "cp -R public/static/js .next/static/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gmail-tester": "^1.1.3",
|
||||
"webpack-bundle-analyzer": "^3.3.2"
|
||||
},
|
||||
"eslint.packageManager": "yarn"
|
||||
|
||||
+23
-21
@@ -1,67 +1,69 @@
|
||||
import React, { Component } from 'react'
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import Router from 'next/router'
|
||||
import { connect } from "react-redux";
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import setAuthorizationHeader from "../data/setAuthorizationHeader";
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import Loader from './../components/templates/loader'
|
||||
import { userLoggedIn, requestLoginFailure } from "../actions/auth";
|
||||
|
||||
import Head from 'next/head'
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
|
||||
|
||||
});
|
||||
|
||||
class AuthRouter extends Component {
|
||||
|
||||
componentDidMount()
|
||||
{
|
||||
componentDidMount() {
|
||||
let JWT = this.props.JWT;
|
||||
if( JWT === null){
|
||||
if (JWT === null) {
|
||||
//free token
|
||||
setAuthorizationHeader()
|
||||
localStorage.setItem('ttJWT', '')
|
||||
this.props.requestLoginFailure('JWT absent')
|
||||
Router.push('/login');}
|
||||
else
|
||||
{
|
||||
Router.push('/Login');
|
||||
}
|
||||
else {
|
||||
localStorage.setItem('ttJWT', JWT)
|
||||
//set token to axios headers
|
||||
setAuthorizationHeader(JWT)
|
||||
var url = localStorage.getItem('loginRedirect')
|
||||
console.log(url)
|
||||
if (url === null)
|
||||
url = '/offres'
|
||||
url = '/Offres'
|
||||
Router.push(url);
|
||||
|
||||
this.props.userLoggedIn()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Fragment>
|
||||
<Head>
|
||||
<title key="title">OAuth authentification router</title>
|
||||
<meta key="robots" name="robots" content="noindex, nofollow"></meta>
|
||||
</Head>
|
||||
<div>
|
||||
<div className="alert alert-info"><CircularProgress color="secondary" />Chargement...</div>
|
||||
<Loader />
|
||||
</div>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
AuthRouter.getInitialProps = function (context) {
|
||||
console.log("--AuthRouter--")
|
||||
const { JWT } = context.query
|
||||
console.log(context.query)
|
||||
//context.store.dispatch(fetchAdRequest(id))
|
||||
//console.log(url)
|
||||
console.log(JWT)
|
||||
|
||||
return {JWT:JWT};
|
||||
|
||||
return { JWT: JWT };
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default withStyles(styles)(connect(null, {userLoggedIn, requestLoginFailure})(AuthRouter));
|
||||
export default withStyles(styles)(connect(null, { userLoggedIn, requestLoginFailure })(AuthRouter));
|
||||
|
||||
|
||||
@@ -0,0 +1,228 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import _ from "lodash"
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Tabs from '@material-ui/core/Tabs';
|
||||
import Tab from '@material-ui/core/Tab';
|
||||
import { TextField, Button, Typography } from '@material-ui/core';
|
||||
import SwipeableViews from 'react-swipeable-views';
|
||||
import Snack from '../components/atoms/snack'
|
||||
import Loader from './../components/templates/loader'
|
||||
import LayoutCandidature from '../components/templates/LayoutCandidature';
|
||||
import CandidatureDetail from '../components/organisms/CandidatureDetail'
|
||||
import OffreDetail from '../components/organisms/OffreDetail'
|
||||
import Chat from '../components/organisms/Chat'
|
||||
import AppBar from '@material-ui/core/AppBar';
|
||||
import { AnswersSelector } from '../reducers/answers'
|
||||
import { fetchAnswerRequest } from '../actions/answer'
|
||||
import Head from 'next/head'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
paddingTop: theme.spacing(20),
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
userDataContainer: {
|
||||
display: 'flex',
|
||||
padding: theme.spacing(2) + 'px 18vw',
|
||||
|
||||
},
|
||||
userTextContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
paddingLeft: theme.spacing(2),
|
||||
},
|
||||
AppBar: {
|
||||
top: '64px',
|
||||
position: 'fixed',
|
||||
},
|
||||
SwipeableViews: {
|
||||
paddingTop: '64px',
|
||||
},
|
||||
tab: {
|
||||
'& span.MuiTab-wrapper': {
|
||||
fontSize: '1.1em',
|
||||
fontWeight: '300',
|
||||
textTransform: 'none',
|
||||
}
|
||||
},
|
||||
title: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '500',
|
||||
},
|
||||
titleBorder: {
|
||||
'&:after': {
|
||||
content: '""',
|
||||
width: '15px',
|
||||
borderBottom: 'solid 2px',
|
||||
borderColor: theme.palette.primary.main + '!important',
|
||||
display: 'flex',
|
||||
},
|
||||
},
|
||||
userTextName: {
|
||||
fontWeight: '500',
|
||||
fontSize: '1em',
|
||||
},
|
||||
userTextDate: {
|
||||
fontWeight: '300',
|
||||
fontSize: '0.9em',
|
||||
},
|
||||
userAvatar: {
|
||||
width: '54px',
|
||||
height: '54px',
|
||||
},
|
||||
menuLogo: {
|
||||
cursor: 'pointer',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: '200px'
|
||||
},
|
||||
[theme.breakpoints.down('sm')]: {
|
||||
width: '130px'
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
});
|
||||
|
||||
function TabContainer({ children, dir }) {
|
||||
return (
|
||||
<Typography component="div" dir={dir} style={{ padding: 8 * 3 }}>
|
||||
{children}
|
||||
</Typography>
|
||||
);
|
||||
}
|
||||
class Candidature extends React.Component {
|
||||
state = {
|
||||
data: {
|
||||
|
||||
},
|
||||
errors: {},
|
||||
loading: true,
|
||||
tabIndex: 0,
|
||||
openSnack: false
|
||||
}
|
||||
|
||||
static getInitialProps(context) {
|
||||
const { id } = context.query
|
||||
return { id: id * 1 }
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
this.props.fetchAnswerRequest(this.props.id)
|
||||
}
|
||||
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
loading: nextProps.answer.loading
|
||||
})
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
|
||||
}
|
||||
|
||||
handleChange = (event, newValue) => {
|
||||
this.setState({ tabIndex: newValue });
|
||||
}
|
||||
|
||||
handleChangeIndex = (index) => {
|
||||
this.setState({ tabIndex: index });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const { data, errors, loading, openSnack } = this.state;
|
||||
|
||||
return (
|
||||
<LayoutCandidature>
|
||||
<Head>
|
||||
<title key="title">Candidature</title>
|
||||
</Head>
|
||||
<Fragment>
|
||||
<div className={classes.container}>
|
||||
{/* {!!errors && (<div className="alert alert-danger">{errors && errors.message}</div> )} */}
|
||||
{loading && (
|
||||
<Loader />
|
||||
)}
|
||||
{!loading && (
|
||||
<Fragment>
|
||||
<AppBar color="default" className={classes.AppBar}>
|
||||
<Tabs
|
||||
value={this.state.tabIndex}
|
||||
onChange={this.handleChange}
|
||||
indicatorColor="primary"
|
||||
variant="fullWidth"
|
||||
>
|
||||
<Tab label="Candidature" className={classes.tab} />
|
||||
<Tab label="Messagerie" className={classes.tab} />
|
||||
<Tab label="Offre d'emploi" className={classes.tab} />
|
||||
</Tabs>
|
||||
</AppBar>
|
||||
<SwipeableViews
|
||||
axis={'x'}
|
||||
index={this.state.tabIndex}
|
||||
onChangeIndex={() => this.handleChangeIndex()}
|
||||
className={classes.SwipeableViews}
|
||||
>
|
||||
<TabContainer dir={'x'}>
|
||||
<Fragment>
|
||||
<section className={classes.userDataContainer}>
|
||||
{this.props.user.picture ?
|
||||
<img alt="talents-tube" src={`http://localhost/uploads/img/${this.props.user.picture}`} className={classes.userAvatar} />
|
||||
:
|
||||
<img alt="talents-tube" src='/static/images/default/account-avatar-default.svg' className={classes.userAvatar} />
|
||||
}
|
||||
<div className={classes.userTextContainer}>
|
||||
<Typography gutterBottom className={classes.userTextName}>
|
||||
{this.props.user.firstname + " " + this.props.user.lastname}
|
||||
</Typography>
|
||||
<Typography gutterBottom className={classes.userTextDate}>
|
||||
Envoyée le {(new Date(this.props.answer.c_time)).toLocaleDateString("fr-FR")}
|
||||
</Typography>
|
||||
</div>
|
||||
</section>
|
||||
{this.props.answer.video && <CandidatureDetail {...this.props.answer} />}
|
||||
</Fragment>
|
||||
</TabContainer>
|
||||
<TabContainer dir={'x'}>
|
||||
{this.props.answer.messages && <Chat answerId={this.props.answer.id} ></Chat>}
|
||||
</TabContainer>
|
||||
<TabContainer dir={'x'}>
|
||||
{this.props.answer.ad && <OffreDetail isCandidature='true' offre={this.props.answer.ad} />}
|
||||
</TabContainer>
|
||||
</SwipeableViews>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<Snack variant="error" message={this.state.errors.message}
|
||||
openSnack={this.state.openSnack} onClose={this.handleClose} />
|
||||
</Fragment>
|
||||
</LayoutCandidature>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return {
|
||||
answer: state.answer,
|
||||
user: state.user
|
||||
}
|
||||
}
|
||||
|
||||
Candidature.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
id: PropTypes.number
|
||||
};
|
||||
|
||||
|
||||
export default withStyles(styles)(connect(mapStateToProps, { fetchAnswerRequest })(Candidature))
|
||||
@@ -2,17 +2,15 @@
|
||||
import React from 'react';
|
||||
import { connect } from "react-redux";
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import _ from "lodash"
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { Typography } from '@material-ui/core';
|
||||
|
||||
import LayoutGrid from "../components/templates/LayoutGrid";
|
||||
import Grid from "../components/templates/Grid"
|
||||
import OffreRow from "../components/organisms/OffreRow"
|
||||
|
||||
import { fetchAdsRequest } from '../actions/ads'
|
||||
import { AdsSelector } from '../reducers/ads'
|
||||
import AnswerRow from "../components/organisms/AnswerRow"
|
||||
import { fetchAdsAnsweredRequest } from '../actions/answer'
|
||||
import { AnswersSelector } from '../reducers/answers'
|
||||
import Head from 'next/head'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
@@ -24,7 +22,16 @@ const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
padding: theme.spacing(0,3),
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'Dosis',
|
||||
fontSize: '1.5em',
|
||||
fontWeight: '600',
|
||||
textAlign: 'center',
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(3),
|
||||
padding: theme.spacing(0, 8),
|
||||
},
|
||||
|
||||
});
|
||||
@@ -36,17 +43,24 @@ class Candidatures extends React.Component {
|
||||
|
||||
return (
|
||||
<LayoutGrid backLink="/">
|
||||
<Head>
|
||||
<title key="title">Candidatures</title>
|
||||
</Head>
|
||||
|
||||
{/* TODO */}
|
||||
<Typography variant="h2" component="h3" align="center" gutterBottom>
|
||||
Mes candidatures //TODO
|
||||
<Typography component="h1" align="center" className={classes.title} gutterBottom>
|
||||
Mes candidatures
|
||||
</Typography>
|
||||
<div className={classes.container}>
|
||||
|
||||
<Grid CustomRowComponent={OffreRow}
|
||||
dataFetcher={this.props.fetchAdsRequest}
|
||||
data={this.props.ads}
|
||||
<Grid CustomRowComponent={AnswerRow}
|
||||
dataFetcher={this.props.fetchAdsAnsweredRequest}
|
||||
data={this.props.answers}
|
||||
currentPage={this.props.currentPage}
|
||||
recordCount={this.props.recordCount}
|
||||
loading={this.props.loading}
|
||||
placeholder={"Rechercher une réponse"}
|
||||
searchable={false}
|
||||
/>
|
||||
</div>
|
||||
</LayoutGrid>
|
||||
@@ -61,11 +75,12 @@ Candidatures.propTypes = {
|
||||
function mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
ads: Object.keys(state.ads).length !== 0 && AdsSelector(state),
|
||||
//ads: state.ads.data,
|
||||
currentPage: _.toNumber(!!state.ads.meta && _.ceil((state.ads.meta.offset + 1) / state.ads.meta.limit)),
|
||||
recordCount: _.toNumber(!!state.ads.meta && state.ads.meta.total_items)
|
||||
loading:state.answers.loading,
|
||||
answers: Object.keys(state.answers).length !== 0 && AnswersSelector(state),
|
||||
//answers: state.answers.data,
|
||||
currentPage: _.toNumber(!!state.answers.meta && _.ceil((state.answers.meta.offset + 1) / state.answers.meta.limit)),
|
||||
recordCount: _.toNumber(!!state.answers.meta && state.answers.meta.total_items)
|
||||
};
|
||||
}
|
||||
|
||||
export default withStyles(styles)(connect(mapStateToProps, { fetchAdsRequest })(Candidatures))
|
||||
export default withStyles(styles)(connect(mapStateToProps, {fetchAdsAnsweredRequest })(Candidatures))
|
||||
|
||||
@@ -0,0 +1,499 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import LayoutBack from '../components/templates/LayoutBack';
|
||||
import Router from 'next/router';
|
||||
import Head from 'next/head'
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
padding: '0 20vw',
|
||||
},
|
||||
bottomContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'Dosis',
|
||||
fontSize: '1.5em',
|
||||
fontWeight: '600',
|
||||
padding: theme.spacing(3, 0),
|
||||
},
|
||||
text: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '300',
|
||||
padding: theme.spacing(1, 0),
|
||||
whiteSpace: 'pre-line',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class Cgu extends Component {
|
||||
render() {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<LayoutBack text="CGU">
|
||||
<Head>
|
||||
<title key="title">Conditions générales d'utilisation</title>
|
||||
<meta key="robots" name="robots" content="noindex, follow"></meta>
|
||||
</Head>
|
||||
<div className={classes.container}>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 1 - DEFINITIONS
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
« Compte(s) Utilisateur(s) » : désigne l'espace personnel de l’Utilisateur sur la Plateforme lui permettant notamment de présenter son profil, de consulter, de partager, de valoriser du Contenu Utilisateur et d’accéder à certains Services.
|
||||
« Conditions Générales d’Utilisation et de Vente » ou « CGUV » : désigne les présentes conditions générales d’utilisation de la Plateforme et de vente des Services associés accessibles sur la Plateforme.
|
||||
« Contenu(s) Utilisateur(s) » : désigne tout contenu (informations, données, messages, images, photographies, vidéos etc.) fourni par les Utilisateurs et hébergé sur la Plateforme.
|
||||
« CV Vidéothèque » : désigne une page dédiée sur la Plateforme contenant l'ensemble des Curriculum Vitae vidéos (ci-après « CV vidéo(s) ») des Talents.
|
||||
« Entreprise(s) » : désigne tout professionnel employeur et/ou recruteur, Utilisateur de la Plateforme.
|
||||
« Plateforme » : désigne la plateforme web de TALENTS TUBE permettant d’accéder aux Services et disponible à l’adresse www.talentubes.com.
|
||||
« Service(s) » : désigne l'ensemble des services gratuits et payants proposés par TALENTS TUBE à l'Utilisateur sur la Plateforme, à savoir notamment :
|
||||
La fourniture d'un compte personnel aux Utilisateurs ;
|
||||
Un service de publication et de notification de Contenu Utilisateur ;
|
||||
Un service de réponse à une offre ou à une demande d'emploi ;
|
||||
Un service de consultation d'une CV Vidéothèque ;
|
||||
La création d'un mini-site (page dédiée sur la Plateforme) à destination des Entreprises.
|
||||
« Talent(s) » : désigne toute personne physique à la recherche d’un emploi, Utilisateur de la Plateforme.
|
||||
« Territoire » : désigne toute institution et/ou organisme représentant une ville, département, région ou toute autre zone géographique délimitée.
|
||||
« Tiers » : désigne toute personne, physique ou morale, extérieure aux présentes CGUV.
|
||||
« Utilisateur(s) » : désigne toute personne utilisant la Plateforme et les Services associés.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 2 - CHAMP D’APPLICATION
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les présentes Conditions Générales d'Utilisation et de Vente ont pour objet de définir les modalités et les conditions dans lesquelles la société TALENTS TUBE (ci-après « TALENTS TUBE ») permet à un Utilisateur d’accéder à la Plateforme et les conditions de délivrance des Services qui y sont proposés.
|
||||
Les présentes CGUV s’appliquent sans restriction ni réserve à toute utilisation de la Plateforme et des Services y associés.
|
||||
L’Utilisateur reconnaît avoir (i) été parfaitement informé des conditions liées les Services, notamment leurs caractéristiques essentielles, et (ii) avoir été invité à consulter les présentes CGUV.
|
||||
L’Utilisateur reconnaît avoir lu et accepté sans réserve les présentes CGUV qui prévalent sur tout autre document.
|
||||
TALENTS TUBE se réserve la possibilité de modifier, à tout moment et sans préavis, les présentes CGUV afin de les adapter aux évolutions des Services, de la Plateforme et/ou de son exploitation. Les modifications des CGUV sont opposables aux Utilisateurs à compter de leur mise en ligne.
|
||||
Les présentes CGUV sont la propriété de TALENTS TUBE. Toute reproduction, même partielle, est interdite.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 3 - PRESENTATION DE LA PLATEFORME ET DESCRIPTION DES SERVICES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
TALENTS TUBE est une Plateforme de mise en relation par la vidéo de l'offre et de la demande d'emploi à destination des Talents, des Entreprises et des Territoires.
|
||||
La Plateforme permet aux Talents, Entreprises et Territoires de mettre en avant leurs compétences, ambitions, objectifs professionnels et valeurs ajoutées diverses par l'intermédiaire notamment d'un support vidéo complet et instantané.
|
||||
A cette fin, TALENTS TUBE met à la disposition des Utilisateurs différents Services gratuits et payants sur la Plateforme et permettant notamment :
|
||||
Le dépôt en ligne d’un CV vidéo par les Talents ;
|
||||
Le dépôt en ligne d’une offre d’emploi en vidéo par les Entreprises ;
|
||||
La mise en avant d’une vidéo Territoire ;
|
||||
L’accès le cas échéant à un CV Vidéothèque ;
|
||||
La création d’une page dédiée sous la forme d’un mini-site pour l’Entreprise ;
|
||||
Un service personnalisé de coaching, de tournage et de montage de CV vidéos. Pour ce Service, l’Utilisateur est invité à consulter les Conditions Générales de Vente applicables figurant à la suite des présentes CGUV.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 4 - SERVICES PAYANTS DE LA PLATEFORME
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
4.1. COMMANDE ET/OU SOUSCRIPTION DE SERVICES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les Services payants de TALENTS TUBE sont disponibles sous la forme d’abonnements et/ou de crédits utilisables directement sur la Plateforme. La description des différentes offres et formules figure sur la Plateforme à l’onglet « Services TALENTS TUBE ».
|
||||
Afin de réaliser la commande et/ou souscription à l’un ou plusieurs des Services payants de TALENTS TUBE, l'Utilisateur devra obligatoirement suivre les étapes suivantes :
|
||||
Créer un compte Utilisateur en se conformant aux instructions données par TALENTS TUBE, en indiquant notamment ses noms, prénoms et renseigner une adresse électronique valide ;
|
||||
Sélectionner le ou les Service(s) souhaité(s) et les différentes options et modalités y relatives le cas échéant ;
|
||||
Compléter le formulaire de commande et/ou de souscription mis à disposition sur la Plateforme ;
|
||||
Vérifier le récapitulatif de la commande et/ou de souscription et le cas échéant, identifier et corriger les erreurs ;
|
||||
Valider la commande et/ou la souscription et le(s) prix ;
|
||||
Suivre les instructions du serveur de paiement en ligne, dans les conditions de l’article 4.3. « Paiement de la commande et/ou de la souscription ».
|
||||
L'Utilisateur recevra par voie électronique et sans délai une confirmation d'acceptation de paiement de la commande et/ou de la souscription, ainsi qu'un accusé réception valant confirmation de la commande et/ou de la souscription.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
4.2. PRIX DES SERVICES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les prix détaillés des Services sont disponibles à l’onglet « Tarifs » de la Plateforme.
|
||||
Les prix des Services payants sont indiqués en euros et s’entendent toutes taxes comprises (TTC) pour les Talents et hors taxes (HT) pour les Entreprises et les Territoires.
|
||||
Le prix de vente des Services est celui en vigueur le jour de la commande et/ou souscription.
|
||||
Le montant total de la commande et/ou de la souscription à un Service payant est indiqué dans le panier de l’Utilisateur ainsi que dans le récapitulatif précédant la validation définitive matérialisée par le bouton « Confirmation avec obligation de paiement ».
|
||||
TALENTS TUBE se réserve le droit de modifier les prix de vente des Services à tout moment. Les Services sont facturés à l’Utilisateur sur la base des prix en vigueur au moment de la validation définitive de la souscription et/ou de la commande par TALENTS TUBE.
|
||||
Aucune commande et/ou souscription ne sera traitée avant la réception effective par TALENTS TUBE du paiement correspondant.
|
||||
TALENTS TUBE accepte les moyens de paiement en ligne suivants : carte bancaire en ligne comptant, prélèvement automatique, virement bancaire.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
4.3. PAIEMENT DE LA COMMANDE ET/OU SOUSCRIPTION
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Pour tout règlement, TALENTS TUBE accepte les moyens de paiement suivants :
|
||||
Paiement par carte bancaire :
|
||||
Le règlement de la commande et/ou souscription par carte bancaire en ligne fait l’objet d’un système de sécurisation des paiements à distance permettant de garantir la confidentialité des informations. Ce système de sécurisation est le paiement 3D Secure, système de paiement par authentification. Le service 3D Secure est gratuit et se déclenche pour les paiements par carte bleue, Visa et Mastercard. La banque vérifie au moment du paiement l’identité de l’Utilisateur payeur et autorise le paiement. Le système d’identification étant spécifique à chaque banque, l’Utilisateur doit vérifier auprès de sa banque le procédé d’authentification utilisé. L’Utilisateur doit s’assurer également auprès de son établissement bancaire que la transaction est acceptée. En cas d’échec de cette dernière et dès la 3ème tentative, des frais bancaires peuvent être facturés à l’Utilisateur.
|
||||
TALENTS TUBE ne peut être tenue responsable des éventuels dysfonctionnements du service de paiement en ligne.
|
||||
Paiement par virement bancaire :
|
||||
No de compte :
|
||||
Numéro IBAN :
|
||||
Code BIC/SWIFT :
|
||||
Nom du compte :
|
||||
Nom et domiciliation de la banque :
|
||||
Merci de bien vouloir préciser dans le libellé de votre virement, votre nom et la référence de la commande.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 5 - INSCRIPTION ET GESTION D’UN COMPTE UTILISATEUR
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
5.1. CONDITIONS D’ACCES ET D’INSCRIPTION
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
L’accès à la Plateforme et l’utilisation des Services sont strictement réservés aux personnes physiques âgées de 18 ans et plus. En accédant, utilisant ou s'inscrivant sur la Plateforme, l'Utilisateur déclare et garantit avoir 18 ans ou plus.
|
||||
L’Utilisateur s’engage à utiliser les Services uniquement et exclusivement à des fins strictement personnelles, étant précisé que toute reproduction, représentation, publication, diffusion ou modification ou extraction du contenu des Services et/ou de la Plateforme est prohibée.
|
||||
L’Utilisateur s’interdit de porter atteinte, de quelque manière que ce soit, au bon fonctionnement des Services et de la Plateforme.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
5.2. CREATION D’UN COMPTE UTILISATEUR
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Pour bénéficier de certains Services, l’Utilisateur est invité à créer au préalable un Compte Utilisateur sur la Plateforme.
|
||||
En accédant aux Services, l’Utilisateur reconnaît avoir reçu préalablement à la création de son Compte Utilisateur, une information claire et compréhensible notamment sur l’identité de la société TALENTS TUBE et sur les caractéristiques essentielles des Services proposés. L’Utilisateur est invité à lire et à accepter les présentes CGUV lors de la création de son Compte Utilisateur et lors de la validation de toute commande et/ou souscription de Services payants.
|
||||
Pour créer un Compte Utilisateur, l’ensemble des champs obligatoires figurant sur le formulaire d’inscription doivent être complétés. L’identifiant et le mot de passe du Compte Utilisateur sont strictement personnels et confidentiels. L’Utilisateur s'engage à garantir la confidentialité de l’identifiant et du mot de passe choisis lors de la création du Compte Utilisateur et s’interdit de les communiquer à des Tiers. L’Utilisateur s'engage à ne pas créer ou utiliser, sous sa propre identité ou celle d’un Tiers, d’autres Comptes Utilisateurs que celui initialement créé.
|
||||
Les Entreprises et Territoires agissant dans le cadre de leur activité professionnelle se portent garants du respect de la présente obligation de confidentialité par tout membre de leur personnel et/ou préposé.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
5.4. VERIFICATION DES INFORMATIONS DE l’UTILISATEUR
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
L'Utilisateur s'engage à fournir des informations personnelles exactes et conformes et à les mettre à jour afin d’en garantir la pertinence et l’exactitude tout au long de l’utilisation de la Plateforme et des Services associés.
|
||||
Ces informations sont notamment, sans que cette énumération soit exhaustive : les noms, prénoms, adresses électroniques, mots de passe ainsi que toute information permettant de renseigner le profil du Talent. Pour les Entreprises et Territoires, il s’agit de la dénomination, forme juridique, siège social et toute information permettant de renseigner les Utilisateurs sur leur identité et activités.
|
||||
TALENTS TUBE se réserve le droit de mettre en place un système de vérification des informations fournies et/ou de demander aux Utilisateurs tout document justificatif pour vérifier l’exactitude des informations communiquées.
|
||||
Ces vérifications ont pour objet de garantir le bon fonctionnement de la Plateforme et des Services et d'assurer à tous les Utilisateurs un environnement de confiance et une sécurité optimale.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
5.5. SUSPENSION OU FERMETURE DU COMPTE UTILISATEUR
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
L'Utilisateur peut à tout moment désactiver son Compte Utilisateur sans préavis et sans motif via l’onglet prévu à cet effet. L’Utilisateur demeure responsable de toutes utilisations faites de la Plateforme sous ses identifiants jusqu'à la fermeture effective de son Compte Utilisateur.
|
||||
La fermeture d’un Compte Utilisateur entraîne la résiliation automatique des Services commandés et/ou souscrits dans les conditions de l’article 7.2. « Résiliation ».
|
||||
TALENTS TUBE se réserve le droit de conserver dans ses archives, dans un délai conforme aux dispositions légales applicables, toutes informations relatives à l'Utilisateur susceptibles de résoudre un litige ou concourant à l'exécution des présentes CGUV. Les informations de l’Utilisateur ne seront cependant plus accessibles sur la Plateforme.
|
||||
Le non-respect par l’Utilisateur des présentes CGUV et/ou l’usage illicite et/ou abusif des Services et/ou de la Plateforme, entraînera le droit pour TALENTS TUBE, d’interrompre, de suspendre ou de supprimer l’accès aux Services et à la Plateforme, sans préjudice du droit à tous dommages et intérêts auxquels TALENTS TUBE pourrait prétendre. Toute suspension ou suppression du Compte Utilisateur sera notifiée à l'Utilisateur par l'envoi d'un courrier électronique.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 6 - REGLES D’UTILISATION DES SERVICES
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
6.1. COMPATIBILITE DU MATERIEL
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Afin de pouvoir bénéficier des Services, l’Utilisateur doit disposer d’un ordinateur PC ou MAC et/ou d’un appareil mobile de type smartphone et/ou d’une tablette sous système d’exploitation et navigateur récents. Avant toute inscription sur la Plateforme, l’Utilisateur s’assure qu’il dispose d’un équipement compatible avec celle-ci.
|
||||
Tout Utilisateur déclare avoir été informé qu'il devra disposer d'un accès à internet souscrit auprès du fournisseur de son choix, dont le coût est à sa charge.
|
||||
L'Utilisateur s'engage à ne pas utiliser de dispositifs ou de logiciels de nature à perturber le bon fonctionnement de la Plateforme et des Services associés.
|
||||
L’Utilisateur fait son affaire personnelle de la bonne qualité et du format adéquat des CV vidéos et plus généralement du Contenu Utilisateur qu’il publie sur la Plateforme.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
6.2. PARTAGE DE CONTENUS
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les Utilisateurs titulaires d'un Compte Utilisateur peuvent soumettre du Contenu Utilisateur notamment pour améliorer les renseignements contenus sur leur profil, communiquer avec d’autres Utilisateurs et préciser certaines informations s'agissant d'une offre ou d'une demande d'emploi.
|
||||
Les Contenus Utilisateurs partagés ne sont pas consultables par les visiteurs non-inscrits et/ou n’ayant pas souscrit aux Services concernés.
|
||||
L’Utilisateur s’engage à agir de manière professionnelle et s’interdit notamment de publier du Contenu Utilisateur inexact, trompeur, diffamatoire, obscène, haineux, discriminatoire, ou inapproprié.
|
||||
Chaque Utilisateur est responsable de son propre Contenu Utilisateur et des conséquences de sa diffusion. Il s'engage à ne publier que des Contenus Utilisateurs comportant des éléments qu'il est légalement autorisé à détenir et utiliser. L'Utilisateur s'interdit de publier sur la Plateforme du Contenu Utilisateur sur lequel des Tiers détiennent des droits (tel que le droit de propriété, le droit d’auteur, le droit au respect de la vie privée, le droit à l'image, etc.).
|
||||
TALENTS TUBE ne peut pas assurer la confidentialité du Contenu Utilisateur publié par un Utilisateur et décline expressément toute responsabilité relative à ce Contenu Utilisateur.
|
||||
TALENTS TUBE se réserve le droit de contrôler tout Contenu Utilisateur avant de valider sa publication sur la Plateforme et de ne pas le publier si celui-ci est contraire aux présentes CGUV, préjudiciable à la Plateforme ou inapproprié pour quelque motif que ce soit.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
6.3. RESTRICTIONS D’UTILISATION DE LA PLATEFORME
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
En accédant à la Plateforme, l'Utilisateur s'engage à faire un usage conforme des Services. Il s'engage à utiliser la Plateforme et les Services de façon loyale, dans le respect des présentes CGUV et conformément aux législations et réglementations en vigueur, à l'ordre public et aux droits des Tiers.
|
||||
À ce titre, l'Utilisateur s'engage à ne pas utiliser la Plateforme et les Services à des fins illicites ou d'une manière constitutive d'une fraude à l'égard de TALENTS TUBE, des autres Utilisateurs ou des Tiers.
|
||||
L'Utilisateur s’interdit notamment :
|
||||
D’utiliser la Plateforme et les Services pour distribuer des courriers électroniques non sollicités ou des publicités ;
|
||||
D’harceler tout autre Utilisateur ;
|
||||
De collecter ou de stocker toute donnée à caractère personnel appartenant à un autre Utilisateur pour d’autres raisons que des échanges d’informations relatives à une procédure de recrutement.
|
||||
L'Utilisateur prend toutes les mesures appropriées pour assurer la sécurité de ses propres données, notamment ses identifiant et mot de passe, et la sauvegarde des informations et du Contenu Utilisateur publié par ses soins.
|
||||
Chaque Utilisateur peut signaler à tout moment un Contenu Utilisateur ou un usage abusif ou toute défaillance de la Plateforme et/ou des Services qui pourrait nuire à sa bonne utilisation dans les conditions précisées ci-après.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
6.4. NOTIFICATIONS
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
L’Utilisateur qui estimerait que certaines informations disponibles sur la Plateforme sont inexactes, voire illicites ou ont un caractère injurieux, discriminatoire ou diffamatoire, est invité à déposer une notification.
|
||||
En application de l’article 6. I. 7 de la loi pour la confiance dans l’économie numérique du 21 juin 2004 (n°2004-575), l’Utilisateur doit porter à la connaissance de TALENTS TUBE tout message faisant l'apologie des crimes contre l'humanité, incitant à la haine raciale, touchant à la pornographie infantile, incitant à la violence ou portant atteinte à la dignité humaine.
|
||||
La notification prend la forme d’un courriel adressé via le formulaire de contact prévu à cet effet dans lequel l’Utilisateur déclinera ses noms, prénoms, adresse et la description des faits litigieux.
|
||||
Formulaire de contact : Cliquez ici.
|
||||
Dans la mesure du possible, l’Utilisateur doit transmettre la copie de la correspondance adressée à l'auteur des informations ou activités litigieuses demandant leur interruption, leur retrait ou leur modification, ou la justification de ce que l'auteur n'a pu être contacté.
|
||||
L’attention de l’Utilisateur, auteur d’une notification, est attirée sur le fait qu’une dénonciation inexacte est susceptible de l’exposer à des sanctions pénales et accepte de garantir TALENTS TUBE des conséquences éventuelles d’une notification abusive.
|
||||
TALENTS TUBE s’engage à réagir promptement à toute notification d’un Utilisateur faite dans les formes et à première vue fondée.
|
||||
TALENTS TUBE veillera à préserver le cas échéant l’anonymat de l’Utilisateur, auteur des propos litigieux, tout en se réservant la faculté de prendre contre lui toute mesure utile.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
6.5. SUPPORT TECHNIQUE
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les Services sont accessibles en continu, 24h/24, 7 jours/7.
|
||||
TALENTS TUBE se réserve toutefois le droit d’interrompre ponctuellement l’accès aux Services pour assurer la sécurité et la maintenance. TALENTS TUBE en informera l’Utilisateur par courrier électronique, par avertissement visible sur la page d’accueil de la Plateforme ou tout autre moyen de communication.
|
||||
L’Utilisateur peut informer TALENTS TUBE à tout moment, de toute difficulté rencontrée au cours de l’utilisation des Services. À cet effet, TALENTS TUBE met à la disposition de l’Utilisateur une adresse de contact lui permettant de communiquer avec le support technique.
|
||||
Contact technique : webmaster@talentstube.com.
|
||||
L’Utilisateur s’interdit de recourir de manière abusive au support technique. TALENTS TUBE se réserve le droit de refuser de répondre à certaines demandes si celles-ci sont de nature à perturber le fonctionnement normal du support technique.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 7 - DROIT DE RETRACTATION - RESILIATION
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
7.1. RETRACTATION
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
L’Utilisateur dispose d'un délai de quatorze (14) jours pour exercer son droit de rétractation, en remplissant un formulaire de rétractation, et ce, sans avoir à motiver sa décision ni à supporter d'autres coûts que ceux prévus aux articles L 221-23 à L. 221-25 dudit Code.
|
||||
L’Utilisateur peut exercer ce droit en remplissant le formulaire annexé aux présentes et en l’adressant par e-mail à contact@talentstube.com.
|
||||
Conformément à l’article L. 221-24 du code de la consommation TALENTS TUBE s’engage à rembourser l’Utilisateur dans les quatorze (14) jours à compter de l’annulation de la commande et/ou souscription.
|
||||
TALENTS TUBE procède au remboursement en utilisant le même moyen de paiement que celui utilisé par l’Utilisateur lors de la commande, sauf accord exprès de l’Utilisateur pour le recours à un autre moyen de paiement, et dans la mesure où le remboursement n'occasionne pas de frais pour l’Utilisateur.
|
||||
L’Utilisateur reconnaît qu’en faisant usage de tout ou partie des Services payants, il renonce formellement au droit de rétractation précité (article L.121-21-8 du code de la consommation). Ainsi, en accédant à certains Services payants, l’Utilisateur sera invité à renoncer expressément à son droit de rétractation.
|
||||
Le présent droit de rétractation n’est pas applicable aux Utilisateurs professionnels tels que les Entreprises et Territoires.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
7.2. RESILIATION
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
L’Utilisateur peut résilier les Services payants à tout moment directement sur la Plateforme par le biais de son Compte Utilisateur. La résiliation sera effective le jour ouvré suivant la validation de la demande de résiliation par TALENTS TUBE.
|
||||
Dans le cadre des abonnements à certains Services, l’Utilisateur ne pourra prétendre au remboursement de son abonnement pour la période payée restant à courir. La résiliation est effective à compter du mois suivant la validation de la résiliation, sous réserve que celle-ci parvienne à TALENTS TUBE au moins 48 heures avant la date de prélèvement de l’abonnement.
|
||||
En cas de manquement simple de l’Utilisateur à ses obligations et notamment aux CGUV, TALENTS TUBE se réserve le droit de résilier la commande et/ou la souscription sous sept (7) jours suivant l’envoi d’une notification par courrier ou par e-mail restée infructueuse. En cas de manquement grave de l’Utilisateur, TALENTS TUBE se réserve le droit de résilier la commande et/ou la souscription sans préavis, ni mise en demeure.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 8 - PROPRIETE INTELLECTUELLE
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
8.1. DROITS DE TALENTS TUBE
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
À l'exception des Contenus Utilisateurs, tout autre contenu disponible sur la Plateforme constitue la propriété exclusive de TALENTS TUBE. La structure générale de la Plateforme, ainsi que les programmes, moteurs de recherche, bases de données, textes, graphiques, images, sons et vidéos, marques le composant, sont la propriété exclusive de TALENTS TUBE.
|
||||
Toute représentation et/ou reproduction et/ou exploitation partielle ou totale des marques appartenant à TALENTS TUBE, des noms de domaine associés et de leurs dérivés orthographiques ou analogiques, de quelque nature que ce soit, est prohibée sans autorisation expresse préalable de TALENTS TUBE.
|
||||
Toute utilisation non expressément autorisée par TALENTS TUBE au titre des présentes est illicite, conformément à l'article L.122-6 du Code de la propriété intellectuelle.
|
||||
Il est notamment interdit à l’Utilisateur de procéder à :
|
||||
Toute représentation, diffusion ou commercialisation des éléments des Services, que ce soit à titre gracieux ou onéreux ;
|
||||
Toute forme d'utilisation des Services de quelque façon que ce soit aux fins de conception, réalisation, diffusion ou commercialisation d’un service similaire, équivalent ou de substitution ;
|
||||
Toute mise à disposition directe ou indirecte de Services au bénéfice d'un Tiers, notamment par location, cession ou prêt, même à titre gratuit sauf en cas d’accord préalable écrit de TALENTS TUBE ;
|
||||
Toute utilisation pour un traitement non autorisé par TALENTS TUBE.
|
||||
De manière générale, l'Utilisateur s'engage à ne pas porter atteinte à l'ensemble des droits de propriété intellectuelle appartenant à TALENTS TUBE ou pour lesquels TALENTS TUBE est titulaire d'une licence d'exploitation.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
8.2. DROITS CONCEDES PAR LES UTILISATEURS A TALENTS TUBE
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les Utilisateurs conservent tous leurs droits de propriété intellectuelle sur le Contenu Utilisateur mais concèdent :
|
||||
À TALENTS TUBE, un droit non exclusif, cessible (y compris le droit de sous-licencier), à titre gracieux, et pour le monde entier, d'utiliser, de représenter, de reproduire, de distribuer, de réaliser des œuvres dérivées et d'exécuter le Contenu Utilisateur au titre des Services ou en relation avec la mise à disposition de ces Services et l'activité de TALENTS TUBE, notamment, sans limitation, pour la promotion et la redistribution de tout ou partie des Services, en tout format, sur tout support et via tous les canaux ;
|
||||
À chaque Utilisateur des Services, le droit non exclusif, à titre gracieux, et pour le monde entier d'accéder au Contenu Utilisateur via les Services, d'utiliser et d'exécuter le Contenu Utilisateur dans la mesure autorisée par les fonctionnalités des Services et par les présentes CGUV.
|
||||
Les droits concédés ci-dessus sur le Contenu Utilisateur (droit d’auteur, droit à l’image etc.) sont consentis pour toute la durée d’utilisation des Services et cessent dès le retrait et/ou la suppression du Contenu Utilisateur et/ou du Compte Utilisateur.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 9 - DONNEES PERSONNELLES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
TALENTS TUBE collecte des données à caractère personnel concernant l’Utilisateur, destinées exclusivement à l’utilisation des Services et qui font l’objet d’un traitement automatisé à des fins techniques et statistiques dans le cadre de la gestion de la Plateforme, des Comptes Utilisateurs ainsi que de l’amélioration des Services.
|
||||
Le traitement des données à caractère personnel de l'Utilisateur a fait l'objet d'une déclaration auprès de la CNIL sous le numéro [•].
|
||||
Les données personnelles de l’Utilisateur ne sont en aucun cas transmises à des Tiers à l’exclusion des cas pour lesquels leur communication est strictement nécessaire à la gestion du Compte Utilisateur et au bon fonctionnement de la Plateforme.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
9.1. Finalité de la collecte
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les données renseignées par l'Utilisateur sur la Plateforme peuvent être consultées par les autres Utilisateurs, à l'exception des informations mentionnées comme étant confidentielles lors de l'inscription.
|
||||
TALENTS TUBE se réserve le droit d'utiliser les données collectées afin de réaliser des analyses statistiques relatives à l'utilisation des Services, des enquêtes de satisfaction pour l’amélioration continue des fonctionnalités de la Plateforme et des opérations de communication et d'animation notamment par l'intermédiaire d'actions de ciblage et de prospection.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
|
||||
9.2. Droits de l'Utilisateur
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Conformément à la loi « Informatique et libertés » du 6 janvier 1978, l’Utilisateur dispose d’un droit d’accès, de rectification et de suppression portant sur les données personnelles le concernant.
|
||||
L’Utilisateur peut exercer ce droit auprès de TALENTS TUBE en adressant une requête soit par voie postale à l'adresse suivante : Place Albert Einstein - CP60 - PIBS 56038 Vannes Cedex, soit par courrier électronique à l'adresse contact@talentstube.com. Cette requête devra être accompagnée d'une pièce d'identité revêtant la signature de l'Utilisateur et devra préciser l'adresse et le moyen par lesquelles une réponse pourra être envoyée.
|
||||
TALENTS TUBE pourra être amenée à conserver les données personnelles des Utilisateurs pendant une période ne pouvant excéder cinq (5) ans à compter de la dernière utilisation. Les données et informations de connexion telles que les pages consultées, les recherches effectuées, les adresses IP et autres seront conservés pendant un (1) an.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 10 - COOKIES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Un cookie est une information déposée sur le disque dur de l’ordinateur de l’Utilisateur de la Plateforme ou par un serveur Tiers. Un cookie permet de mémoriser les choix faits par un Utilisateur et de reconnaître facilement l’Utilisateur par le biais d’un identifiant unique.
|
||||
Pour la gestion du contenu de sa Plateforme, TALENTS TUBE dépose des cookies sur le terminal de l’Utilisateur.
|
||||
Les cookies utilisés ont pour finalité :
|
||||
La mémorisation des identifiants de session ;
|
||||
L’authentification de l’Utilisateur.
|
||||
Conformément à l’article 32-II de la loi du 6 janvier 1978, TALENTS TUBE informe l’Utilisateur par un bandeau que la poursuite de sa navigation vaut accord pour l’installation et la lecture de cookies.
|
||||
Les cookies déposés par TALENTS TUBE sont nécessaires à la fourniture d'un service expressément demandé par l'Utilisateur (cookies identifiants de session, authentification de l’Utilisateur, etc.). Ils ne feront pas l’objet d’un recueil de consentement préalable.
|
||||
Néanmoins, l’Utilisateur peut refuser l’installation de cookies déposés par TALENTS TUBE sur le disque dur de son ordinateur en sélectionnant les paramètres appropriés dans son navigateur.
|
||||
Des cookies supplémentaires sont déposés par TALENTS TUBE pour réaliser des opérations de publicité ciblée, des mesures d’audience ou pour générer des partages de réseaux sociaux. Dans de telles hypothèses, TALENTS TUBE recueille expressément le consentement de l’Utilisateur, qui dispose de la faculté de refuser les cookies déposés.
|
||||
Les cookies sont conservés par TALENTS TUBE pendant douze (12) mois. A l’expiration de ce délai, TALENTS TUBE recueille de nouveau, quand il est nécessaire, le consentement de l’Utilisateur.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 11 - CONFIDENTIALITE
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
TALENTS TUBE s’engage à mettre en œuvre des moyens raisonnables pour assurer la sécurité et la confidentialité des données échangées au titre de l’utilisation des Services.
|
||||
Chaque Utilisateur respecte la confidentialité des informations auxquelles il a accès sur la Plateforme, notamment les informations relatives aux autres Utilisateurs. Il s'engage également à respecter leur vie privée et leur Contenu Utilisateur.
|
||||
Chaque Utilisateur s'interdit de collecter des informations concernant les autres Utilisateurs en particulier les adresses électroniques, de quelque façon que ce soit, sans leur consentement préalable et à quelle que fin que ce soit.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 12 - RESPONSABILITE
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
La responsabilité de TALENTS TUBE relève du régime de l’hébergeur pour tout Contenu Utilisateur qui ne fait pas l’objet d’un contrôle préalable à sa publication sur la Plateforme, et du régime de l’éditeur pour le Contenu Utilisateur vérifié par ses équipes avant publication sur celle-ci.
|
||||
TALENTS TUBE met en œuvre tous les moyens raisonnables à sa disposition pour assurer une diffusion et un accès de qualité aux Services. À ce titre et pour l’ensemble des obligations à sa charge, TALENTS TUBE est tenue d’une obligation de moyens.
|
||||
TALENTS TUBE ne saurait être tenue responsable des dommages, directs ou indirects, qui pourraient survenir en raison de l’utilisation non conforme aux présentes des Services.
|
||||
TALENTS TUBE ne sera pas responsable des dommages indirects, accessoires, particuliers, consécutifs ou punitifs ou de toute perte de profits ou de revenus, qu'ils soient occasionnés directement ou indirectement, ou de toute perte de données ou d'autres pertes irrécupérables, résultant de l'accès ou l'utilisation ou l’incapacité d'accéder ou d'utiliser les Services ainsi que de l'accès, l'utilisation ou la modification non autorisés des transmissions ou contenus.
|
||||
En tout état de cause, la responsabilité de TALENTS TUBE est limitée aux dommages prévisibles et directs subis exclusivement par l’Utilisateur.
|
||||
TALENTS TUBE ne garantit ni l’aptitude des Talents ni, de manière plus générale la véracité, la qualité ou la pertinence des Contenus Utilisateurs. TALENTS TUBE ne saurait être tenue responsable des dommages, pertes et frais, réclamations ou dépenses supportés ou encourus par l’Entreprise du fait du recrutement ou de l’engagement d’un Talent. À cet égard, l’Entreprise se chargera de la vérification de tout document, attestation et diplôme demandé à un Talent ou présenté par lui, et renonce à tout recours contre TALENTS TUBE.
|
||||
La Plateforme peut contenir des liens hypertextes vers des sites internet ou des ressources provenant de Tiers. A ce titre, chaque Utilisateur reconnait que TALENTS TUBE n'est pas responsable de la disponibilité ou de l’exactitude de ces sites internet ou ressources, du contenu, des produits ou des services disponibles sur ou à partir de ces sites internet ou ressources. La présence de liens vers de tels sites internet ou ressources n’indique en aucun cas que TALENTS TUBE les approuve ni dans leur contenu, ni s'agissant des produits ou services qui y sont offerts. L'Utilisateur reconnait être seul responsable et assumer tout risque lié à l’utilisation de tous sites internet ou ressources, ou du contenu, des produits ou des services offerts par ces sites internet ou ressources.
|
||||
L'Utilisateur reconnait agir pour son propre compte, de telle manière que TALENTS TUBE ne pourra être considéré comme un agent à son égard. Les présentes CGUV ne constituent ni une association, ni une entreprise commune, ni une franchise, ni un partenariat, ni une relation de subordination, ni un mandat donné par l'Utilisateur à TALENTS TUBE.
|
||||
L’Utilisateur est responsable de l’utilisation de son compte en ligne et de la conservation du caractère personnel et confidentiel de son identifiant et mot de passe. A ce titre, l’Utilisateur s’engage à signaler par écrit à TALENTS TUBE toute perte ou divulgation d’élément susceptible de permettre à un Tiers de prendre connaissance des identifiants de connexion et veille à se déconnecter du compte à la fin de chaque session.
|
||||
TALENTS TUBE ne peut être tenue responsable de tout dysfonctionnement du réseau ou des serveurs ou de tout autre événement échappant au contrôle raisonnable, qui empêcherait ou dégraderait l’accès aux Services.
|
||||
TALENTS TUBE décline toute responsabilité en cas d'indisponibilité des Services résultant d'un cas de force majeure, notamment :
|
||||
Anomalies quelconques du matériel informatique de l’Utilisateur ;
|
||||
Faits imprévisibles et insurmontables d'un Tiers au contrat ;
|
||||
Indisponibilité du réseau internet.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 13 - DIVISIBILITE DES CONDITIONS GENERALES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
S'il advenait, pour quelque cause que ce soit, qu'une des clauses des présentes CGUV ne puisse être appliquée, notamment par suite d'annulation judiciaire, elle serait alors réputée non écrite, sans pour autant entraîner la nullité des CGUV, ni altérer la validité de ses autres dispositions.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="center">
|
||||
ARTICLE 14 - DROIT APPLICABLE ET LITIGES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="center">
|
||||
{
|
||||
`
|
||||
Les présentes CGUV sont régies et soumises au droit français.
|
||||
L’Utilisateur et TALENTS TUBE s’efforceront de résoudre à l’amiable les contestations qui pourraient surgir relatives à la validité, l’interprétation ou l’exécution des présentes.
|
||||
À défaut d’accord amiable, l’Utilisateur a la possibilité de recourir à une procédure de médiation conventionnelle ou à tout autre mode alternatif de règlement des différends.
|
||||
Si l’Utilisateur est un professionnel, les tribunaux du ressort de la Cour d’Appel de RENNES seront seuls compétents pour connaître de tout litige relatif à l’exécution et/ou à l’interprétation des CGUV, y compris en cas d’appel en garantie et de pluralité de défendeurs.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
</div>
|
||||
</LayoutBack>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default withStyles(styles)(Cgu);
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
import _ from "lodash"
|
||||
import Grid from "../components/templates/Grid"
|
||||
import LayoutGrid from "../components/templates/LayoutGrid"
|
||||
import { Typography } from '@material-ui/core';
|
||||
import CompanyRow from "../components/organisms/CompanyRow"
|
||||
|
||||
import { fetchCompaniesRequest } from '../actions/companies'
|
||||
import { CompaniesSelector } from '../reducers/companies'
|
||||
import { connect } from "react-redux";
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
paddingTop: theme.spacing(20),
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
class Companies extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<LayoutGrid backLink="/">
|
||||
<div className={classes.container}>
|
||||
<Grid CustomRowComponent={CompanyRow}
|
||||
dataFetcher={this.props.fetchCompaniesRequest}
|
||||
data={this.props.companies}
|
||||
currentPage={this.props.currentPage}
|
||||
recordCount={this.props.recordCount}
|
||||
>
|
||||
</Grid>
|
||||
</div>
|
||||
</LayoutGrid>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Companies.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
function mapStateToProps(state) {
|
||||
|
||||
return {
|
||||
companies: Object.keys(state.companies).length !== 0 && CompaniesSelector(state),
|
||||
// //companies: state.companies.data,
|
||||
currentPage: _.toNumber(!!state.companies.meta && _.ceil((state.companies.meta.offset + 1) /state.companies.meta.limit) ),
|
||||
recordCount: _.toNumber(!!state.companies.meta && state.companies.meta.total_items)
|
||||
};
|
||||
}
|
||||
export default withStyles(styles)(connect(mapStateToProps, { fetchCompaniesRequest })(Companies));
|
||||
@@ -1,77 +0,0 @@
|
||||
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Plyr from 'react-plyr';
|
||||
import _ from "lodash/string";
|
||||
import Ar from "lodash/array";
|
||||
|
||||
import api from "../data/api"
|
||||
import Layout from "../components/templates/Layout";
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
paddingTop: theme.spacing(20),
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '20vh',
|
||||
},
|
||||
video: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '33vh',
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
class Company extends React.Component {
|
||||
static async getInitialProps(context) {
|
||||
console.log("Company ")
|
||||
const { id } = context.query
|
||||
let res = await api.companies.fetchCompany(id)
|
||||
console.log(res)
|
||||
return { data: res };
|
||||
}
|
||||
|
||||
onVimeoError = (err) => {
|
||||
console.error(err);
|
||||
}
|
||||
render() {
|
||||
const { classes, data } = this.props;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={classes.video}>
|
||||
{data.presentation_video && (<Plyr autoplay
|
||||
type="vimeo" // or "vimeo"
|
||||
videoId={Ar.last(_.split(data.presentation_video.cdn_url, '/'))}
|
||||
/>)}
|
||||
</div>
|
||||
<div className={classes.container}>
|
||||
<Typography gutterBottom variant="title" >
|
||||
{data.brand_name}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.container}>
|
||||
<Typography gutterBottom variant="subheading" >
|
||||
{data.description}
|
||||
</Typography>
|
||||
</div>
|
||||
{/* TODO:list of ads */}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Company.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Company);
|
||||
@@ -0,0 +1,362 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import LayoutBack from '../components/templates/LayoutBack';
|
||||
import Link from 'next/link'
|
||||
import Head from 'next/head'
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
padding: '0 20vw',
|
||||
},
|
||||
bottomContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignSelf: 'left',
|
||||
justifyContent: 'left',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'Dosis',
|
||||
fontSize: '1.5em',
|
||||
fontWeight: '600',
|
||||
padding: theme.spacing(3, 0),
|
||||
},
|
||||
text: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '300',
|
||||
padding: theme.spacing(1, 0),
|
||||
whiteSpace: 'pre-line',
|
||||
},
|
||||
textCookies: {
|
||||
|
||||
},
|
||||
textCookies: {
|
||||
|
||||
},
|
||||
link: {
|
||||
color: theme.palette.primary.main,
|
||||
textDecoration: 'none',
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class Confidentialite extends Component {
|
||||
render() {
|
||||
const { classes } = this.props
|
||||
return (
|
||||
<LayoutBack text="Politique de confidentialité et protection des données">
|
||||
<Head>
|
||||
<title key="title">Politique de confidentialité et protection des données</title>
|
||||
<meta key="robots" name="robots" content="noindex, follow"></meta>
|
||||
</Head>
|
||||
<div className={classes.container}>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
Nous vous invitons à lire avec attention le présent document. Pour toute question concernant ce document et, d’une manière générale, sur la collecte et le traitement de vos informations personnelles par la société talentstube.com, n’hésitez pas à nous contacter via le formulaire présent sur la page contact : <a href="https://blog.talentstube.com/contact.html" className={classes.link} target="_blank">https://blog.talentstube.com/contact.html</a>
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
DONNÉES PERSONNELLES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Dans le cadre de son activité, talents tube est amené à collecter, traiter et stocker vos données personnelles.
|
||||
Conformément aux dispositions légales, et en particulier à la Loi n°78-17 du 6 janvier 1978, et au Règlement (UE) 2016/679 du Parlement européen et du Conseil du 27 avril 2016 applicable à compter du 25 mai 2018 (dit RGPD), talents tube tient à garantir la confidentialité et la sécurité de vos données à caractère personnel.
|
||||
C’est pourquoi, nous nous engageons à :
|
||||
Utiliser et traiter les données qui vous concerne en toute transparence, loyauté et légalité,
|
||||
Limiter le traitement des données à caractère personnel, leur conservation, leur collecte ainsi que leur stockage,
|
||||
Garantir l'exactitude de vos données et vous permettre de demander leur suppression ou leur rectification,
|
||||
Assurer la sécurité, l'intégrité et la confidentialité de vos données.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
UTILISATION DES DONNEES COLLECTEES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
L'utilisation des données est déterminée, légitime et explicite :
|
||||
Les données collectées pour une finalité précise ne sont pas utilisées à d’autres fins. Nos finalités sont en adéquation avec les normes de conformité de la Cnil (Commission nationale de l’informatique et des libertés).
|
||||
À titre d’exemple, les données collectées sur le candidat lors de inscription sur le site talentstube.com sont les données nécessaires au bon déroulement du processus de recrutement. Avec l’objectif :
|
||||
« De permettre à chaque candidat de trouver/ d’être orienté vers les offres les plus susceptibles de correspondre à ses attentes, son profil ; de postuler à ces offres ; d’être contacté par des recruteurs cherchant à pourvoir des emplois ; et in fine de trouver un emploi. »
|
||||
La finalité définie permet de déterminer la pertinence des données que nous allons collecter. Seules les données adéquates et strictement nécessaires pour atteindre la finalité seront collectées et traitées. Ainsi nous ne collectons que les données permettant :
|
||||
au candidat de valoriser sa candidature
|
||||
au candidat d’exprimer ses préférences
|
||||
au candidat de décrire son parcours
|
||||
au recruteur d’évaluer le candidat
|
||||
au recruteur de trouver les candidats correspondant à ses critères de recrutement
|
||||
d’éviter les usurpations d’identité
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
Quelles données collectons-nous ?
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Pour le candidat :
|
||||
Nom/Prénom
|
||||
Mail
|
||||
Téléphone
|
||||
Réseaux sociaux
|
||||
Éventuellement d’autres informations renseignées dans les champs libres
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
Quelles données collectons-nous ?
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Nous nous engageons à ne pas divulguer à des tiers les informations et Données Personnelles que vous nous communiquez. Ces informations et Données Personnelles sont exclusivement utilisées par nos services internes et ne seront en aucun cas cédées, vendues à des tiers sans votre accord préalable exprès.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
DUREE DE CONSERVATION DES DONNEES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
La durée de conservation des données à caractère personnel est variable selon la finalité de la collecte. Ainsi, les données sont conservées pendant un temps défini et nécessaire à la réalisation de leur finalité. Aussi, selon le type de Données Personnelles, la durée de conservation peut varier entre quelques jours et plusieurs années.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
SECURITE
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Talents tube s’engage à assurer la protection des données personnelles par la mise en place de mesures techniques et organisationnelles appropriées.
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
VOS DROITS
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Vous disposez d'un droit d'accès, de rectification, de suppression, d’opposition et de portabilité des données. Nous disposons d’un délai de 30 jours à compter de votre demande pour apporter une réponse.
|
||||
Vous pouvez demander une limitation du traitement des données collectées. L’exercice de ces droits peut exercer ce droit auprès de TALENTS TUBE en adressant une requête soit par voie postale à l'adresse suivante : 3 Rue Louis de Broglie, Village By CA 56000 Vannes, soit par courrier électronique via le formulaire présent sur la page Contact `
|
||||
}<a href="https://blog.talentstube.com/contact.html" className={classes.link} target="_blank">https://blog.talentstube.com/contact.html</a>{`. Cette requête devra être accompagnée d'une pièce d'identité revêtant la signature de l'Utilisateur et devra préciser l'adresse et le moyen par lesquelles une réponse pourra être envoyée..
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
COOKIES
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Le site www.talentstube.com utilise des cookies. Il s’agit de petits fichiers téléchargés sur votre ordinateur qui permettent au site de fonctionner de manière fluide et sécurisée (cookies de session et de sécurité) et qui enregistrent des informations sur la façon dont nos visiteurs consultent notre site (cookies de pistage). Cela nous permet par exemple de savoir quelles sont les pages les plus consultées par nos visiteurs et combien de temps ces derniers passent sur notre site.
|
||||
En paramétrant votre navigateur, vous avez la possibilité de désactiver les cookies ou d’être alerté lorsque des cookies sont sur le point d’être téléchargés sur votre ordinateur. Cependant, la désactivation de tous les cookies pourrait dans certains cas vous empêcher d’utiliser correctement toutes les fonctionnalités de ce site.
|
||||
Vous avez la possibilité de désactiver les cookies en vous rendant dans les options de votre navigateur. Cliquez sur le lien correspondant à votre navigateur pour découvrir comment faire :
|
||||
`
|
||||
}
|
||||
<a className={classes.link} href="https://support.google.com/chrome/answer/95647?hl=fr" target="_blank">Google Chrome</a><br />
|
||||
<a href="https://support.mozilla.org/fr/kb/activer-desactiver-cookies" className={classes.link} target="_blank">Mozilla Firefox</a><br />
|
||||
<a href="http://support.apple.com/kb/PH17191?viewlocale=fr_FR&locale=en_US" className={classes.link} target="_blank">Safari</a><br />
|
||||
<a href="http://help.opera.com/Windows/10.20/fr/cookies.html" className={classes.link} target="_blank">Opera</a><br />
|
||||
<a href="http://windows.microsoft.com/fr-fr/internet-explorer/delete-manage-cookies#ie=ie-11" className={classes.link} target="_blank">Internet Explorer</a><br />
|
||||
|
||||
`
|
||||
Pour plus d’informations sur les cookies et la façon de les gérer, nous vous recommandons de consulter le site `} <a href="http://www.youronlinechoices.com/fr/" className={classes.link} target="_blank">www.youronlinechoices.com</a>{`, développé par l’organisme EDAA (European Interactive Digital Advertising Alliance).
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
Google Analytics
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Ce site utilise le service d’analyse web Google Analytics. Cela nous permet d’analyser la façon dont les internautes utilisent notre site afin de leur proposer une meilleure expérience de navigation. Pour ce faire, Google Analytics utilise des cookies de pistage permettant de suivre le parcours d’un internaute sur le site. Ces cookies sont conservés pour une durée de 26 mois.
|
||||
Nous avons activé l’anonymisation des adresses IP pour ce site. Cela signifie que les adresses IP collectées par Google Analytics sont systématiquement raccourcies pour rendre impossible l’identification des internautes. Dans certains cas exceptionnels, l’adresse IP complète peut être transférée sur un serveur de Google localisé aux Etats-Unis, et est ensuite raccourcie immédiatement sur celui-ci. Google utilise les informations récoltées via les cookies pour le compte de talents tube, dans le but d’analyser les habitudes de navigation des internautes et de fournir des rapports sur l’activité du site.
|
||||
Google met à disposition des utilisateurs un plug-in pour leur navigateur leur permettant d’empêcher la récolte de données relatives à leur navigation sur les sites web utilisant Google Analytics.
|
||||
Ce plug-in est disponible à cette adresse : `} <a href="https://tools.google.com/dlpage/gaoptout?hl=fr" className={classes.link} target="_blank">tools.google.com/dlpage/gaoptout</a>{`
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
Google Remarketing
|
||||
</Typography>
|
||||
<Typography className={classes.text} gutterBottom align="left">
|
||||
{
|
||||
`
|
||||
Ce site peut être amené à utiliser le service Google Remarketing, qui permet de proposer aux internautes des annonces publicitaires ciblées en fonction des sites consultés dans le réseau d’annonces de Google. Cela nous permet notamment d’utiliser vos données de navigation sur ce site afin de vous proposer des annonces ciblées lors que vous visitez d’autres sites du réseau Google. Vous avez la possibilité de désactiver cette fonctionnalité via le lien suivant : `} <Link href="http://www.google.com/settings/ads"><a className={classes.link} target="_blank">www.google.com/settings/ads</a></Link> {`
|
||||
Si vous possédez un compte Google et que vous êtes connecté à celui-ci en visitant notre site, et si vous avez autorisé Google à lier votre historique de navigation à votre compte Google et d’utiliser cet historique pour personnaliser les annonces, Google liera les données de navigation recueillies grâce à Google Analytics à votre compte Google. Si vous utilisez votre compte Google sur plusieurs appareils, nos annonces pourront s’afficher sur ces autres appareils. Bien que Google collecte sur ce site des données liées à l’utilisation de comptes Google, talents tube ne peut en aucun cas en déduire l’identité des utilisateurs de comptes Google consultant le site.
|
||||
Pour plus d’informations concernant la façon dont Google collecte et utilise ces données, nous vous invitons à consulter les règles de confidentialité de Google :`} <Link href="http://www.google.com/policies/privacy/"><a className={classes.link} target="_blank">www.google.com/policies/privacy</a></Link> {`
|
||||
`
|
||||
}
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.title} component="h2" gutterBottom align="left">
|
||||
Autres cookies
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
PHPSESSID
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Conserve la configuration des paramètres des utilisateurs à travers les demandes de page.
|
||||
Fournisseur : talentstube.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
__cfduid
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Utilisé par le réseau de contenu, Cloudlare, pour identifier le trafic web fiable.
|
||||
Fournisseur : crisp.chat
|
||||
Expiration : 1 an
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
lang
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Rappelle la version linguistique d'un site sélectionnée par l'utilisateur.
|
||||
Fournisseur : ads.linkedin.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
_ga
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Enregistre un identifiant unique utilisé pour générer des données statistiques sur la façon dont le visiteur utilise le site.
|
||||
Fournisseur : talents tube.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
_gat
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Utilisé par Google Analytics pour diminuer radicalement le taux de requêtes.
|
||||
Fournisseur : talents tube.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
_gid
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Enregistre un identifiant utilisé pour générer des données statistiques sur le façon dont le visiteur utilise le site.
|
||||
Fournisseur : talents tube.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
collect
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Collecte des données pour mesurer de façon efficace les annonces vues ou cliquées. Il permet d'afficher des annonces ciblées.
|
||||
Fournisseur : Google.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
vuid
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Recueille des données sur les visites de l'utilisateur sur le site web, telles que les pages qui ont été consultées.
|
||||
Fournisseur : vimeo.com
|
||||
Expiration : 2 ans
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
BizoID
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Statistiques publicitaires LinkedIn
|
||||
Fournisseur : ads.linkedin.com
|
||||
Expiration : 29 jours
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
bscookie
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Utilisé par le service de réseau social, LinkedIn , pour le suivi de l'utilisation des services intégrés.
|
||||
Fournisseur : ads.linkedin.com
|
||||
Expiration : 2 ans
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
_hjIncludedInSample
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Détermine si la navigation de l'utilisateur doit être enregistrée dans un certain espace réservé pour les statistiques.
|
||||
Fournisseur : talentstube.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
lidc
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Utilisé par le service de réseau social, LinkedIn , pour le suivi de l'utilisation des services intégrés.
|
||||
Fournisseur : ads.linkedin.com
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
r/collect
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Ce cookie est utilisé pour envoyer des données à Google Analytics sur l'appareil et le comportement du visiteur. Il piste le visiteur à travers différents appareils et canaux marketing.
|
||||
Fournisseur : doubleclick.net
|
||||
Expiration : Session
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
UserMatchHistory
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Ce cookie est utilisé pour envoyer des statistiques publicitaires LinkedIn.
|
||||
Fournisseur : ads.linkedin.com
|
||||
Expiration : 29 jours
|
||||
</Typography>
|
||||
|
||||
<Typography className={classes.titleCookies} component="h3" gutterBottom align="left">
|
||||
cookie.js
|
||||
</Typography>
|
||||
<Typography className={classes.textCookies} component="h3" gutterBottom align="left">
|
||||
Non determiné
|
||||
Fournisseur : talentstube.com
|
||||
Expiration : Sessions
|
||||
</Typography>
|
||||
</div>
|
||||
</LayoutBack>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
export default withStyles(styles)(Confidentialite);
|
||||
@@ -0,0 +1,126 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import { connect } from 'react-redux';
|
||||
import { requestConfirmationAccountRequest } from '../actions/user';
|
||||
import Loader from './../components/templates/loader'
|
||||
import Typography from '@material-ui/core/Typography'
|
||||
import Button from '@material-ui/core/Button';
|
||||
import LayoutBack from '../components/templates/LayoutBack';
|
||||
import Router from 'next/router';
|
||||
import Head from 'next/head'
|
||||
|
||||
const styles = theme => ({
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
height: '100%',
|
||||
},
|
||||
bottomContainer: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignSelf: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
},
|
||||
title: {
|
||||
fontFamily: 'Dosis',
|
||||
fontSize: '1.5em',
|
||||
fontWeight: '600',
|
||||
padding: theme.spacing(3, 0),
|
||||
},
|
||||
text: {
|
||||
fontSize: '1em',
|
||||
fontWeight: '300',
|
||||
padding: theme.spacing(1, 0),
|
||||
},
|
||||
bottomText: {
|
||||
fontWeight: '500',
|
||||
},
|
||||
button: {
|
||||
textTransform: 'inherit',
|
||||
marginTop: theme.spacing(4),
|
||||
marginBottom: theme.spacing(2),
|
||||
padding: theme.spacing(1.2, 1),
|
||||
borderRadius: '3px',
|
||||
color: theme.palette.secondary.main,
|
||||
backgroundColor: theme.palette.primary.important,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
class ConfirmAccount extends Component {
|
||||
state = {
|
||||
|
||||
errors: {},
|
||||
loading: false,
|
||||
|
||||
};
|
||||
UNSAFE_componentWillReceiveProps(nextProps) {
|
||||
this.setState({
|
||||
errors: nextProps.errors,
|
||||
loading: nextProps.loading,
|
||||
|
||||
})
|
||||
}
|
||||
render() {
|
||||
|
||||
const { classes } = this.props
|
||||
const { loading } = this.state
|
||||
return (
|
||||
<LayoutBack text="Validation" height="fixed">
|
||||
<Head>
|
||||
<title key="title">Confirmation de compte</title>
|
||||
<meta key="robots" name="robots" content="noindex, nofollow"></meta>
|
||||
</Head>
|
||||
<div className={classes.container}>
|
||||
{loading && (
|
||||
<Loader />
|
||||
)}
|
||||
{!loading && (
|
||||
<Fragment>
|
||||
<Typography gutterBottom align="center" className={classes.title}>
|
||||
e-mail validé
|
||||
</Typography>
|
||||
<Typography gutterBottom align="left" className={classes.text}>
|
||||
Votre email a bien été validé.
|
||||
</Typography>
|
||||
<Typography gutterBottom align="left" className={classes.text}>
|
||||
Vous pouvez maintenant vous connecter.
|
||||
</Typography>
|
||||
<section className={classes.bottomContainer}>
|
||||
<Button
|
||||
variant="contained"
|
||||
className={classes.button}
|
||||
classes={{ root: classes.rootButton }}
|
||||
onClick={() => Router.push('/Login')}
|
||||
>
|
||||
Connexion
|
||||
</Button>
|
||||
</section>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
</LayoutBack>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
return {
|
||||
loading: !!state.user.loading,
|
||||
errors: state.user.errors
|
||||
};
|
||||
}
|
||||
|
||||
ConfirmAccount.getInitialProps = function (context) {
|
||||
const { token, email } = context.query
|
||||
context.store.dispatch(requestConfirmationAccountRequest({ 'confirmationToken': token, 'email': email }))
|
||||
return email;
|
||||
}
|
||||
|
||||
|
||||
export default withStyles(styles)(connect(mapStateToProps, { requestConfirmationAccountRequest })(ConfirmAccount));
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user