Compare commits

...

629 Commits

Author SHA1 Message Date
Huaoe 901280363e supp import
tt-app/app/saga Build queued...
2019-11-05 14:10:26 +01:00
Huaoe 6612f91047 push notification fixed 2019-11-04 16:36:20 +01:00
Huaoe 5e7fabe1ce Merge branch 'webRTCIntegration' into saga 2019-11-04 14:10:50 +01:00
Huaoe e05aadd5c3 fix slash positionning in /api/talent?bearer= 2019-11-04 12:26:27 +01:00
Huaoe 567a282dcf Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-11-04 10:25:08 +01:00
Sébastien 3eb5f6852f Fix: Home card height responsive
tt-app/app/Saga There was a failure building this commit
2019-10-29 11:45:48 +01:00
Grégoire 7ddab7d95c Redirect to monCompte on website 2019-10-29 11:04:24 +01:00
Huaoe 400340aaef repair Reponse + public folder usage
tt-app/app/webRTCIntegration There was a failure building this commit
2019-10-18 12:20:09 +02:00
Huaoe 5a43a2a9e1 upload webrtc + Link stalling 2019-10-18 10:54:10 +02:00
Huaoe 69848dbe38 made this url work
tt-app/app/webRTCIntegration There was a failure building this commit
: http://localhost:3000/Reponse?adId=80
2019-10-17 17:24:52 +02:00
Huaoe ad40f73997 webRtc repaired 2019-10-16 16:34:05 +02:00
Grégoire fbdc460ee0 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
tt-app/app/Saga There was a failure building this commit
2019-10-16 09:15:40 +02:00
Grégoire e30c7f8daf fix console.info 2019-10-16 09:15:06 +02:00
Grégoire 9a87bc6627 Fix bug offres pourvues / offres en cours 2019-10-16 09:14:37 +02:00
Huaoe a2042e48e7 captcha rehearse 2019-10-15 09:45:41 +02:00
Huaoe cc02119938 Recaptcha fix
tt-app/app/Saga There was a failure building this commit
2019-10-14 17:04:31 +02:00
Huaoe b2affbd87b repa 2019-10-14 11:15:44 +02:00
Huaoe 0526286554 right version 2019-10-14 11:15:18 +02:00
Huaoe 2b5e024bd0 fix quotes 2019-10-14 10:31:44 +02:00
Huaoe ed74d6f882 fix paramaeteres 2019-10-14 10:19:52 +02:00
Grégoire ff2225a47b Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-10-14 10:16:45 +02:00
Grégoire 23832d2c4f save subscription 2019-10-14 10:15:23 +02:00
Sébastien 655bce3fca Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
tt-app/app/Saga There was a failure building this commit
tt-app/app/ALPHA There was a failure building this commit
2019-10-11 16:13:59 +02:00
Sébastien 1c33d5f7cf Feat: Add Slider dependencies 2019-10-11 16:13:55 +02:00
Sébastien 3737e34f32 Feat: Update next config for slider (not working) 2019-10-11 16:13:13 +02:00
Sébastien 36644308b8 Feat: Add slider component 2019-10-11 16:12:46 +02:00
Grégoire 080deea1e5 add fetchUserSubscriptionToPush ( old:isUserSubscribedToPush) 2019-10-11 15:16:04 +02:00
Sébastien a0caba4009 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-10-11 14:17:23 +02:00
Grégoire 3c1deffbf1 merge work 2019-10-11 12:21:35 +02:00
Grégoire c07482e340 add isUserSubscribedToPush 2019-10-11 12:05:06 +02:00
Huaoe cc80821658 fix nofollow 2019-10-11 11:24:44 +02:00
Huaoe b0cbafdf44 fix nofollow 2019-10-11 11:24:29 +02:00
Grégoire 6869085f6d Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-10-11 11:00:46 +02:00
Huaoe 9f8e61069c fix reducer after onUpdateSubscriptionOnServer 2019-10-11 09:26:36 +02:00
Sébastien 0b6b8451d1 Feat: Add new component slider 2019-10-11 09:21:44 +02:00
Grégoire 17041758cb Add visit record companies and ads pages 2019-10-10 16:56:15 +02:00
Sébastien ddfbd344f6 Feat: update placeholder 2019-10-10 09:02:18 +02:00
Huaoe 64d750fb9e comment 2019-10-09 17:19:54 +02:00
Huaoe ebe9f62a8f reset search button 2019-10-09 11:49:46 +02:00
Huaoe fd8cb20f52 merge seb changes 2019-10-09 11:42:52 +02:00
Huaoe 7526059543 search fix 2019-10-09 11:36:41 +02:00
Sébastien d0bd5103cf Fix: content height
tt-app/app/Saga There was a failure building this commit
2019-10-09 11:34:41 +02:00
Sébastien c9a3ae62ce Fix: Change recaptcha key 2019-10-09 11:34:20 +02:00
Sébastien 7d2ca2bad3 Feat: add phone field 2019-10-09 11:34:01 +02:00
Sébastien ab59d8872d Fix: edit style 2019-10-09 11:33:37 +02:00
Sébastien 4da821ce79 Feat: add prop height 2019-10-09 11:32:46 +02:00
Grégoire 501af4b3f6 Chat : video in bubble 2019-10-09 10:50:45 +02:00
Huaoe 3c61032cf0 publicRuntimeConfig changes 2019-10-09 10:18:18 +02:00
Huaoe af4e3207b4 User picture update final 2019-10-09 09:45:51 +02:00
Huaoe 3b835a4461 add upload picture : not working 2019-10-08 17:29:25 +02:00
Huaoe f5a1fce6ac changes after arret de travail 2019-10-07 09:49:16 +02:00
Huaoe 604b030045 avant arret travail 2019-10-07 09:17:34 +02:00
Sébastien 9857a2363f Feat: Add currentjob and phone
tt-app/app/Saga There was a failure building this commit
2019-10-04 13:46:48 +02:00
Sébastien 892084cd41 Feat: Update README informations about integration 2019-10-04 13:43:29 +02:00
Sébastien 47fca4b73a Feat: add react-google-recaptcha for forms 2019-10-04 13:42:58 +02:00
Sébastien d1c865dbd8 Feat: Update icon of input 2019-10-04 13:42:09 +02:00
Sébastien f35b9d2037 Feat: Add condition for territories and Tags of offer 2019-10-04 13:41:48 +02:00
Sébastien a4e039706d Feat: Add arrow to home slider 2019-10-04 13:40:59 +02:00
Sébastien 13f4790a36 Feat: add Crisp to app 2019-10-04 13:36:54 +02:00
Sébastien 829b1476d6 Feat: Add Recaptcha on login form 2019-10-01 18:09:42 +02:00
Sébastien 5c579cb592 Feat: territory style 2019-10-01 18:09:05 +02:00
Sébastien 2464d4b6bc Fix: Tile size 16:9 2019-10-01 18:08:34 +02:00
Sébastien cb6729aa77 Fix: image width 2019-10-01 18:07:46 +02:00
Sébastien d396fcc83a Fix: import video from library 2019-10-01 18:07:30 +02:00
Sébastien d888c173f4 Feat: Add territory part
tt-app/app/Saga There was a failure building this commit
2019-09-26 11:46:21 +02:00
Sébastien 9d0f25984e Feat: Add current page link color 2019-09-26 11:45:26 +02:00
Sébastien f2550a2df7 Feat: Add new prop to VideoVimeo 2019-09-26 11:44:45 +02:00
Sébastien 8f034b61de Fix: height bug 2019-09-26 11:43:42 +02:00
Sébastien 53ccf11d87 Fix: little fix 2019-09-26 11:43:27 +02:00
Sébastien fcfae5408b Feat: Enable linkedin twitter and website in form 2019-09-26 11:42:31 +02:00
Huaoe 370e58494c fe
tt-app/app/Saga There was a failure building this commit
2019-09-20 15:24:18 +02:00
Huaoe 9d5fbedb2c sh
tt-app/app/Saga There was a failure building this commit
2019-09-20 15:19:35 +02:00
Huaoe cf7d4fc6bc test
tt-app/app/Saga There was a failure building this commit
2019-09-20 14:56:36 +02:00
Huaoe a960426a6d gitignore stalling 2019-09-20 11:31:28 +02:00
Huaoe 87862a26df .gitignore is now working 2019-09-20 11:29:32 +02:00
Huaoe c29681cb7c dirt
tt-app/app/Saga There was a failure building this commit
2019-09-20 10:56:33 +02:00
Huaoe f01af4813e jenkins sucks 2019-09-20 10:27:28 +02:00
Huaoe de9a4f7b38 jenkins hell
tt-app/app/Saga There was a failure building this commit
2019-09-20 10:12:06 +02:00
Huaoe b394f5fa5d jenkinsfile
tt-app/app/Saga There was a failure building this commit
2019-09-20 09:54:40 +02:00
Huaoe 9d9c777c93 volume out
tt-app/app/Saga There was a failure building this commit
2019-09-19 16:56:59 +02:00
Huaoe bddf254005 without Dockerfile
tt-app/app/Saga There was a failure building this commit
2019-09-19 16:56:37 +02:00
Huaoe 1a90223e22 jenkins file docker image config
tt-app/app/Saga There was a failure building this commit
2019-09-19 16:49:11 +02:00
Huaoe b963814067 jenkins 2019-09-19 16:48:31 +02:00
Huaoe 921030d6c0 gitignore accepting Jenkins
tt-app/app/Saga There was a failure building this commit
2019-09-19 15:05:07 +02:00
Huaoe 80e03c29e9 jenkins README 2019-09-19 14:39:40 +02:00
Huaoe 6cb252e031 cypress test stalling 2019-09-19 14:39:27 +02:00
Huaoe bfbb31ce25 Next.js 9 stalling + new DB + cypress stalling 2019-09-19 10:55:03 +02:00
Huaoe 849e0a5b18 next.js 9 installed ! 2019-09-18 14:57:10 +02:00
Huaoe 9f497b83d5 SEO optim
tt-app/app/Saga There was a failure building this commit
2019-09-17 10:02:18 +02:00
Huaoe 344bdca41c SEO optim 2019-09-17 09:41:04 +02:00
Huaoe a05780ee39 tag manager try fix 2019-09-16 15:28:57 +02:00
Huaoe f7ac5e669f tag 2019-09-16 14:37:32 +02:00
Huaoe b28ec325b8 noscript 2019-09-16 09:21:05 +02:00
Huaoe 6a6bfa3e39 merge
tt-app/app/Saga There was a failure building this commit
2019-09-13 16:50:30 +02:00
Huaoe 753c990fc6 12 items entreprise 2019-09-13 16:46:56 +02:00
Sébastien 8069a54d1a Fix: logo border radius 2019-09-13 12:16:05 +02:00
Huaoe 9e2e58d45c gitignore 2019-09-13 11:54:42 +02:00
Sébastien e7c59003cf Fix: Add login redirect 2019-09-13 11:46:54 +02:00
Sébastien 8c435db886 Feat: Add new page 2019-09-13 11:41:17 +02:00
Sébastien 2d41de8568 Fix: align arrow, change font size etc 2019-09-13 11:41:05 +02:00
Sébastien a4a5096eb8 Fix: bottom bar hover on mobile 2019-09-13 11:40:09 +02:00
Sébastien d7128d7ddc Fix: Add new box shadow 2019-09-13 11:39:27 +02:00
Sébastien e06b23e73a Feat: add new default picture company 2019-09-13 11:38:24 +02:00
Huaoe 90aefa56c2 camel case in iFRame video 2019-09-13 10:54:19 +02:00
Huaoe 6ceae61685 entreprise pagesize 12 2019-09-13 10:29:41 +02:00
Sébastien d8033ec4ef Feat: Add props title
tt-app/app/Saga There was a failure building this commit
2019-09-12 12:07:05 +02:00
Sébastien b4b9a0f5c4 Fix: card height 2019-09-12 12:06:35 +02:00
Sébastien d4933d9535 Fix: menu height of logo part 2019-09-12 12:06:21 +02:00
Sébastien 024c34753c Fix: menu width 2019-09-12 12:06:03 +02:00
Sébastien d348f0d618 Fix: video_title 2019-09-12 12:05:51 +02:00
Sébastien 4662d0b30d Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-09-12 11:30:33 +02:00
Sébastien e6b873b861 Fix: add condition to display this part 2019-09-12 11:30:24 +02:00
Sébastien 88d0f8a222 Fix: Menu override with width 50% 2019-09-12 11:30:00 +02:00
Sébastien 15b94ea080 Feat: Update box shadow animation 2019-09-12 11:29:28 +02:00
Sébastien e4218c708e Fix: TT logo width 2019-09-12 11:28:43 +02:00
Sébastien 772aa99c21 Fix: delete useless files 2019-09-12 11:28:31 +02:00
Sébastien e1c6729d04 Fix: Card size 2019-09-12 11:28:16 +02:00
Sébastien 4bd910fe6b Fix: vimeo autoplay 2019-09-12 11:27:51 +02:00
Huaoe fe1b554dce Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
tt-app/app/Saga There was a failure building this commit
2019-09-12 11:02:55 +02:00
Huaoe a75b4509a0 agent { dockerfile true } 2019-09-12 11:01:49 +02:00
Sébastien e36330bcc2 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-09-12 10:37:10 +02:00
Sébastien 51b36943ef Fix: Card adjustment and horizontal scroller width 2019-09-12 10:36:52 +02:00
Sébastien 78f80b4d7b Feat: Add new background 2019-09-12 10:34:10 +02:00
Sébastien 65beb482c2 Fix: search bar width and cursor on button 2019-09-12 10:33:45 +02:00
Sébastien ec7cab4b98 Fix: link and font size 2019-09-12 10:33:22 +02:00
Sébastien 2089bd5c76 Fix: font size 2019-09-12 10:31:38 +02:00
Sébastien 36cfa25d09 Fix: box shadow override 2019-09-12 10:30:34 +02:00
Sébastien 2394319605 Feat: Add new background color 2019-09-12 10:30:15 +02:00
Sébastien fb002efaec Fix: title position center 2019-09-12 10:30:03 +02:00
Sébastien 4b49371b08 Fix: menu logo width 2019-09-12 10:29:46 +02:00
Sébastien b5261ff528 Feat: Add new content 2019-09-12 10:29:32 +02:00
Huaoe 61f03f4208 mhart
tt-app/app/Saga There was a failure building this commit
2019-09-12 10:10:14 +02:00
Huaoe d140bfeb38 install
tt-app/app/Saga There was a failure building this commit
2019-09-12 09:40:59 +02:00
Huaoe edb1ba680a cdd
tt-app/app/Saga There was a failure building this commit
2019-09-12 09:15:21 +02:00
Huaoe b3f927a46f kiki
tt-app/app/Saga There was a failure building this commit
2019-09-12 09:03:55 +02:00
Huaoe 288b8b1f51 cd cd 2019-09-12 09:03:23 +02:00
Huaoe 416eb4129c cd
tt-app/app/Saga There was a failure building this commit
2019-09-12 09:00:23 +02:00
Huaoe 613f0e755f test
tt-app/app/Saga There was a failure building this commit
2019-09-12 08:49:13 +02:00
Huaoe ed8562f95b node:node
tt-app/app/Saga There was a failure building this commit
2019-09-11 17:28:58 +02:00
Huaoe c95775194d su
tt-app/app/Saga There was a failure building this commit
2019-09-11 17:25:00 +02:00
Huaoe 783b65b5e9 sudo
tt-app/app/Saga There was a failure building this commit
2019-09-11 17:22:15 +02:00
Huaoe 38fef5c514 node_modules
tt-app/app/Saga There was a failure building this commit
2019-09-11 17:21:12 +02:00
Huaoe e6286c97e4 node
tt-app/app/Saga There was a failure building this commit
2019-09-11 17:19:34 +02:00
Huaoe 55a2979814 echpo
tt-app/app/Saga There was a failure building this commit
2019-09-11 17:16:13 +02:00
Huaoe b5dc00eb7b tres
tt-app/app/Saga Something is wrong with the build of this commit
2019-09-11 17:05:27 +02:00
Huaoe 813c085e3b chown
tt-app/app/Saga Something is wrong with the build of this commit
2019-09-11 16:55:39 +02:00
Huaoe 550688a99e change docker image
tt-app/app/Saga Build queued...
2019-09-11 16:49:41 +02:00
Huaoe 061e756237 steps
tt-app/app/Saga Something is wrong with the build of this commit
2019-09-11 16:46:27 +02:00
Huaoe d6091fb496 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
tt-app/app/Saga There was a failure building this commit
2019-09-11 16:44:16 +02:00
Huaoe 52221edbee test1 2019-09-11 16:42:52 +02:00
Sébastien 4cf4347611 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
tt-app/app/Saga There was a failure building this commit
2019-09-11 16:23:59 +02:00
Sébastien 60c9fa9b6f Add new pages cgu mentions etc 2019-09-11 16:23:29 +02:00
Huaoe 80c48533d7 testset
tt-app/app/Saga There was a failure building this commit
2019-09-11 16:02:11 +02:00
Huaoe 3dfd5d2c89 test
tt-app/app/Saga There was a failure building this commit
2019-09-11 15:53:29 +02:00
Huaoe ba4118be38 test re
tt-app/app/Saga Something is wrong with the build of this commit
2019-09-11 15:01:58 +02:00
Huaoe 9967eab3c5 test again
tt-app/app/Saga There was a failure building this commit
2019-09-11 14:47:14 +02:00
Huaoe 861a0bf45f jenkins script call
tt-app/app/Saga There was a failure building this commit
2019-09-11 14:42:40 +02:00
Huaoe 1b7089a694 jenkins file modif test
tt-app/app/Saga There was a failure building this commit
2019-09-11 14:32:05 +02:00
Huaoe d6e345b2f3 test
tt-app/app/Saga Build queued...
2019-09-11 14:28:35 +02:00
Huaoe d720b08ccb jenkins readme and makefile windows compat 2019-09-11 14:12:33 +02:00
Huaoe b773d04e4d Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-09-11 13:56:04 +02:00
Sébastien bd247c9ebd Jenkins: update jenkinsfile
tt-app/app/Saga There was a failure building this commit
2019-09-11 12:32:15 +02:00
Sébastien e08c0f3605 Jenkins: update jenkinsfile
tt-app/app/Saga There was a failure building this commit
2019-09-11 11:57:40 +02:00
Sébastien f86fcfe3d8 Jenkins: Update jenkinsfile
tt-app/app/Saga There was a failure building this commit
2019-09-11 11:48:04 +02:00
Sébastien 6629cd8156 Fix: External link -> talentstube.com 2019-09-11 11:39:18 +02:00
Huaoe 5a29559ee7 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-09-11 11:05:36 +02:00
Huaoe eab5e57e4b brand_name everywhere 2019-09-11 11:05:31 +02:00
Sébastien 90ce3b1e6d Fix: general box shadow hover 2019-09-11 10:53:26 +02:00
Sébastien 97a438ac39 Fix: box shadow hover animation
tt-app/app/Saga Build queued...
2019-09-11 10:32:39 +02:00
Sébastien a804655113 Fix: text align left 2019-09-11 10:32:19 +02:00
Sébastien 079cc18f5f Fix: zIndex value 2019-09-11 10:31:45 +02:00
Huaoe 18abd46de6 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-09-11 09:36:10 +02:00
Sébastien 4728107d9a Jenkins: Update scripts path 2019-09-11 08:24:19 +02:00
Sébastien 52f5c361d2 Jenkins: Update jenkinsfile
tt-app/app/Saga There was a failure building this commit
2019-09-10 16:21:55 +02:00
Sébastien efcd8002d5 Jenkins: add conf files
tt-app/app/Saga Something is wrong with the build of this commit
2019-09-10 16:00:25 +02:00
Sébastien 2d9017bd5d Update gitignore 2019-09-10 16:00:09 +02:00
Sébastien 7637ffd11c Fix: height of ad card on mobile 2019-09-10 14:26:37 +02:00
Sébastien b7fa1c403e Fix: chip width (fit-content) 2019-09-10 11:58:29 +02:00
Sébastien 48936e7150 Fix: Company card size (height and chips position) 2019-09-10 11:41:36 +02:00
Sébastien 3dcd837a9e Fix: video responsive 2019-09-10 11:41:05 +02:00
Huaoe a859e045cd google tag manager added 2019-09-10 10:37:42 +02:00
Sébastien 3a4637bc1a Jenkins: add conf files 2019-09-10 08:44:35 +02:00
Sébastien a230f02005 Jenkins: add conf files 2019-09-10 08:44:16 +02:00
Sébastien 3c4e06a84e Add README file 2019-09-10 08:43:27 +02:00
Sébastien 0c8b1b5fe5 Jenkins: add conf files
tt-app/app/Saga There was a failure building this commit
2019-09-06 16:22:45 +02:00
Sébastien 74b4de0543 Jenkins: move scripts and JenkinsFile to nextjs folder 2019-09-06 16:18:39 +02:00
Sébastien 730a61cddb Jenkins: update Jenkinsfile 2019-09-06 15:47:45 +02:00
Sébastien df7f3604f9 Jenkins: add config files 2019-09-06 15:43:34 +02:00
Sébastien 15e74aab9f Update scripts 2019-09-06 15:41:59 +02:00
Sébastien 7dd7071a0a Add readme file 2019-09-06 15:41:41 +02:00
Sébastien 8393f22ea6 Update docker config 2019-09-06 15:41:27 +02:00
Sébastien 985ed33346 Jenkins: add conf files
tt-app/app/Saga There was a failure building this commit
2019-09-06 09:31:05 +02:00
Sébastien c357846ecf Cypress: Test output when running in command line 2019-09-06 09:30:25 +02:00
Sébastien 7685f304bf Jenkins: add conf files 2019-09-06 09:28:35 +02:00
Sébastien 327d9d05c4 Add scripts sh 2019-09-06 09:26:24 +02:00
Sébastien f577733522 Add new pipeline 2019-09-06 09:22:10 +02:00
Sébastien 2a82a3758e Update test script 2019-09-06 09:21:46 +02:00
Sébastien 80f7ef2da4 Jenkins: add conf files
tt-app/app/Saga This commit looks good
2019-09-05 11:41:17 +02:00
Sébastien 1ac0b286a9 Fix: zIndex problems on Menu and Home video 2019-09-05 11:40:27 +02:00
Sébastien 20805afd31 Fix: checkbox color 2019-09-05 11:39:54 +02:00
Sébastien 47376ff7b7 Add image for help page 2019-09-05 11:39:39 +02:00
Sébastien a787a1f870 Delete useless images 2019-09-05 11:39:27 +02:00
Sébastien 2ca49a9566 Add image 2019-09-05 11:37:23 +02:00
Sébastien a4961638f2 Add Jenkinsfile 2019-09-05 11:37:02 +02:00
Huaoe 583ab9480f app_dev purge 2019-09-04 16:10:55 +02:00
Huaoe 73bf25904f test server 2019-09-02 16:07:33 +02:00
Huaoe 5206050f6a add server logs 2019-09-02 15:44:07 +02:00
Huaoe 5ca4abd2ac sitemap gen 2019-08-30 17:09:55 +02:00
Huaoe 9bfc3ac023 sitemapgenerator 2019-08-30 17:04:26 +02:00
Huaoe 088ad46a2f Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-30 16:40:14 +02:00
Huaoe d115add2ea tests 2019-08-30 16:39:57 +02:00
Sébastien 9cdb2889d5 Jenkins: add missing file 2019-08-30 16:32:38 +02:00
Sébastien 5cd1391ade Jenkins: add plugin Gogs 2019-08-30 16:32:19 +02:00
Sébastien e74e915060 fix: zindex problem 2019-08-30 16:12:44 +02:00
Sébastien e94f20ef78 Fix: side of message 2019-08-30 16:12:33 +02:00
Sébastien 25a335f2f0 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-30 15:52:22 +02:00
Sébastien 26d243f658 Add: Jenkins missing files 2019-08-30 15:52:17 +02:00
Sébastien 3c0702022d Add Jenkins folder and container 2019-08-30 15:51:13 +02:00
Huaoe 9bfe4fb198 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-30 15:50:18 +02:00
Huaoe 90b56c0471 test cypress 2019-08-30 15:49:28 +02:00
Sébastien 28bb8e5299 Fix: width of content 2019-08-30 08:28:20 +02:00
Huaoe a18accb39c small changes 2019-08-29 16:41:29 +02:00
Sébastien 092b6cb655 Fix: overflow-x to overflow 2019-08-29 16:10:29 +02:00
Sébastien 24d733b4f7 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-29 16:06:25 +02:00
Sébastien e3170e07dd Fix: video width overflow 2019-08-29 16:06:20 +02:00
Huaoe bda41ff48b Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-29 14:44:06 +02:00
Huaoe bf9be45efd console.log purge 2019-08-29 14:44:02 +02:00
Sébastien 323fe4b2c1 Add vimeo video 2019-08-29 14:21:32 +02:00
Sébastien 81938e714d Fix: vimeo video slider width 2019-08-29 14:21:18 +02:00
Sébastien 6ab1b74342 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-29 11:32:35 +02:00
Sébastien 1bedb3e467 Fix: bug video 2019-08-29 11:31:48 +02:00
Huaoe b1cd82c346 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-29 11:21:52 +02:00
Huaoe a8df08e2f2 clean Plyr 2019-08-29 11:21:38 +02:00
Sébastien 6b2a99caee Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/components/atoms/VideoVimeo.js
2019-08-29 11:05:13 +02:00
Sébastien fbaf665ce9 Update cypress 2019-08-29 11:02:06 +02:00
Sébastien 4cfd8bde6f Update props 2019-08-29 11:01:34 +02:00
Sébastien b6e1a8b864 Add video 2019-08-29 11:01:18 +02:00
Huaoe 80eeb91f79 dynamic usage for first contentful paint optim 2019-08-29 09:53:53 +02:00
Huaoe 0063ef68f5 the end of Plyr 2019-08-29 09:33:26 +02:00
Huaoe eb6d281332 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-28 09:47:36 +02:00
Huaoe ff0bb50414 try dynamic for time to interactive shortennings matters 2019-08-28 09:47:31 +02:00
Huaoe fb528ebc84 url mangling 2019-08-28 09:31:05 +02:00
Sébastien a0504ec5fb Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/components/organisms/Chat.js
2019-08-28 09:15:01 +02:00
Sébastien e40110a038 Update: script cypress + restoredb + backupdb 2019-08-28 09:12:35 +02:00
Sébastien 7512ed04e2 Fix: chat message position 2019-08-28 09:12:09 +02:00
Sébastien 5c23dc39bf Fix: shit enter in input type text 2019-08-28 09:11:56 +02:00
Sébastien 5488844845 Add cypress class 2019-08-28 09:11:31 +02:00
Sébastien 33670ee1d6 Fix height bug and add cypress class 2019-08-28 09:11:11 +02:00
Sébastien f569559f56 Update cypress id 2019-08-28 09:10:49 +02:00
Sébastien d4083e4701 Add cypress order with file name 2019-08-28 09:09:52 +02:00
Sébastien ee808cffb2 Fix offer card width 2019-08-28 09:08:32 +02:00
Huaoe 99f1c4d117 url mangling entreprise fix 2019-08-28 09:00:02 +02:00
Huaoe a54e85d359 .env 2019-08-27 16:55:52 +02:00
Huaoe d10d03e6bb seo url mangling setup 2019-08-27 16:49:27 +02:00
Huaoe 1d959f88f2 classes fix 2019-08-27 12:21:29 +02:00
Sébastien 31d566a9c7 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-27 09:37:41 +02:00
Sébastien 127e5e9d8d Fix: chat style 2019-08-27 09:37:35 +02:00
Sébastien c57b1402e9 Fix width of card when 1 result 2019-08-27 09:37:20 +02:00
Sébastien 3c759d3572 Fix scroll bug 2019-08-27 09:37:00 +02:00
Sébastien 51deea06b8 Fix spacing between candidatures 2019-08-27 09:36:46 +02:00
Huaoe b9ba409c99 register typo 2019-08-27 09:36:01 +02:00
Huaoe 77d1f9954e head fix 2019-08-26 17:00:20 +02:00
Sébastien a3ad2c0030 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/components/atoms/ChatCard.js
#	nextjs/components/organisms/Chat.js
2019-08-26 16:28:00 +02:00
Sébastien bc95306a15 Add chat style 2019-08-26 16:23:33 +02:00
Huaoe 45efd6b556 head title everywhere 2019-08-26 16:22:33 +02:00
Huaoe 1ddbc65e14 console.log purge 2019-08-26 15:44:13 +02:00
Huaoe da90a3283a chat 2019-08-26 15:16:06 +02:00
Huaoe efaf7ef6dd Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-26 11:16:41 +02:00
Huaoe 9999cc3417 mesasge 2019-08-26 11:16:37 +02:00
Sébastien f931c25a9e Little fix 2019-08-26 11:12:34 +02:00
Sébastien 0ac6abb5b5 Add condition to imgUrl and imgUrlDesktop 2019-08-26 11:12:26 +02:00
Sébastien 79b8223c8c Add style to checkbox 2019-08-26 11:12:06 +02:00
Thomas Berrod 0633ece7ba entreprise dust broom 2019-08-21 12:01:22 +02:00
Thomas Berrod a474f0740f OGHead done 2019-08-21 10:39:28 +02:00
Thomas Berrod 65e2ab90d2 update package.json for linkedin bug 2019-08-20 09:54:07 +02:00
Thomas Berrod a7d31d6f4d git blocked linkedin bug 2019-08-19 16:54:46 +02:00
Thomas Berrod 042394d87d webpush dust 2019-08-19 14:34:31 +02:00
Thomas Berrod 9d2fefe4ed push provider integrated 2019-08-16 17:26:38 +02:00
Thomas Berrod 3a08038da5 provider working 2019-08-16 16:40:39 +02:00
Thomas Berrod 4cfad7bbca push standalone in param page 2019-08-13 17:27:50 +02:00
Thomas Berrod cf8f8086d7 push -foloowings 2019-08-13 15:21:17 +02:00
Thomas Berrod 36f7d5698b push - begin 2019-08-12 17:14:34 +02:00
Thomas Berrod b0fd71ee0f Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-12 09:21:47 +02:00
Thomas Berrod 0f8fe8cfd8 candidature spontanée avancement 2019-08-12 09:21:37 +02:00
Sébastien GLATZ c0e209ab67 Add next previous style 2019-08-09 16:30:40 +02:00
Sébastien GLATZ 6c091c3fac Fix useless padding 2019-08-09 14:08:34 +02:00
Sébastien GLATZ 8a7b425e90 Fix responsive 2019-08-09 14:02:50 +02:00
Sébastien GLATZ 7ee7ab7e90 Add bottom spacing for discover container 2019-08-09 14:02:11 +02:00
Sébastien GLATZ ae603ba41d Remove link style 2019-08-09 14:01:54 +02:00
Sébastien GLATZ 58c36dbf2d Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-09 12:22:52 +02:00
Sébastien GLATZ d70ab851aa Add url to access account 2019-08-09 12:22:45 +02:00
Thomas Berrod de57d33a17 comment switch 2019-08-09 12:15:33 +02:00
Thomas Berrod 3caa419579 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-09 12:14:48 +02:00
Thomas Berrod fd9bc060cc hack back 2019-08-09 12:09:50 +02:00
Sébastien GLATZ d705f062ed Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-09 12:09:41 +02:00
Sébastien GLATZ a15a2ad831 Add new page parametres 2019-08-09 12:09:35 +02:00
Thomas Berrod 4372c7a1e5 hack to change file name 2019-08-09 12:09:27 +02:00
Sébastien GLATZ d0dd09cce6 Fix screen orientation card 2019-08-09 12:08:45 +02:00
Thomas Berrod db5670ffe2 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-09 11:42:48 +02:00
Thomas Berrod dd6e2cdf4b reset handle snack off 2019-08-09 11:42:36 +02:00
Sébastien GLATZ 154fe260dc Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-09 10:56:59 +02:00
Sébastien GLATZ 583e568085 Add style 2019-08-09 10:56:52 +02:00
Thomas Berrod 3a137d4282 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-09 10:56:50 +02:00
Thomas Berrod 9c47f93e17 reset 2019-08-09 10:56:45 +02:00
Sébastien GLATZ 2b585216da Fix component VideoVimeo name 2019-08-09 10:43:27 +02:00
Sébastien GLATZ 77be2fd0c9 Fix: form width and responsive 2019-08-09 10:40:29 +02:00
Sébastien GLATZ 96ccc64eae Little fix 2019-08-09 10:28:56 +02:00
Sébastien GLATZ 9e9527686f Fix video vimeo width 2019-08-09 10:26:37 +02:00
Sébastien GLATZ 6b0cb02afc Fix title height 2019-08-09 10:25:57 +02:00
Sébastien GLATZ 4f5b03cdff Add footer 2019-08-09 10:25:01 +02:00
Thomas Berrod 63042e0e83 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-08 16:43:12 +02:00
Thomas Berrod 35ef8f28c0 misc 2019-08-08 16:43:07 +02:00
Sébastien GLATZ a9c3d382cf Fix line break 2019-08-08 16:42:14 +02:00
Sébastien GLATZ de45ba36c6 Add new conditional color 2019-08-08 16:28:11 +02:00
Sébastien GLATZ 7b227fb1f5 Fix: align center icons 2019-08-08 16:27:56 +02:00
Sébastien GLATZ b2e75da6f8 Fix logo container border size and radius 2019-08-08 16:27:43 +02:00
Sébastien GLATZ 7aaf059e55 Fix title top spacing 2019-08-08 16:26:57 +02:00
Sébastien GLATZ 608283a6e6 Fix searchbar background color 2019-08-08 16:26:35 +02:00
Sébastien GLATZ 9235571dcb Fix header style 2019-08-08 16:26:15 +02:00
Sébastien GLATZ 5e806b6c64 Add style and border 2019-08-08 16:26:01 +02:00
Sébastien GLATZ 989d215ef8 Fix: link region 2019-08-08 16:25:32 +02:00
Thomas Berrod 9daacb05d6 fix index in list 2019-08-08 16:19:48 +02:00
Sébastien GLATZ b152cb5aea Fix: scrollbar on chrome 2019-08-08 14:44:53 +02:00
Sébastien GLATZ 9ec2bf5cb6 Add hover effect on card 2019-08-08 14:44:43 +02:00
Sébastien GLATZ b8b18d5403 Fix: style 2019-08-08 13:45:12 +02:00
Sébastien GLATZ cfc895b5dd Fix: box shadow 2019-08-08 13:43:45 +02:00
Sébastien GLATZ 287170ac96 Fix: video width 2019-08-08 13:43:29 +02:00
Sébastien GLATZ 6b636d0af3 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/components/organisms/Chat.js
2019-08-08 11:47:27 +02:00
Sébastien GLATZ bd0e394d8a Add new layout for message input 2019-08-08 11:45:18 +02:00
Thomas Berrod 7f3764ae67 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-08 11:37:42 +02:00
Thomas Berrod 7363e9133b bug fix : Register + login 2019-08-08 11:37:38 +02:00
Thomas Berrod 4d17137922 uniquement les offres publiées sur entreprise detail 2019-08-08 09:44:13 +02:00
Sébastien GLATZ fa24a9b292 Little style fix 2019-08-08 09:05:07 +02:00
Sébastien GLATZ 4d637240aa Delete horizontal scrollbar 2019-08-08 08:58:20 +02:00
Sébastien GLATZ 2b617226f6 Fix: Appbar bug on scroll and scroll in page 2019-08-08 08:57:50 +02:00
Sébastien GLATZ 988440f5fd Fix: change title when promoted 2019-08-08 08:57:29 +02:00
Thomas Berrod 9d0d9f04ff chat saga building 2019-08-07 17:10:21 +02:00
Sébastien GLATZ ab76048563 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-07 15:43:22 +02:00
Sébastien GLATZ 62e8f483c9 Fix: avatar border radius 2019-08-07 15:43:14 +02:00
Sébastien GLATZ 0ab872a2db Little style fix 2019-08-07 15:43:01 +02:00
Sébastien GLATZ ed38d28fc5 Fix: cursor pointer 2019-08-07 15:42:37 +02:00
Sébastien GLATZ 2368f3f520 Fix: app name 2019-08-07 15:41:41 +02:00
Sébastien GLATZ 7a861a77d1 Fix: content width 2019-08-07 15:41:26 +02:00
Thomas Berrod f21bbadb5b wait : comment upload photo 2019-08-07 15:24:21 +02:00
Thomas Berrod beab21d889 background color android 2019-08-07 15:03:07 +02:00
Sébastien GLATZ c77ec3c9b0 Fix appbar 2019-08-07 11:20:20 +02:00
Sébastien GLATZ d8c289a6a8 Add VideoVimeo component and Fix button postuler 2019-08-07 11:20:02 +02:00
Sébastien GLATZ 77ba7c5f94 Add VideoVimeo component 2019-08-07 11:18:58 +02:00
Thomas Berrod 39baa0ab66 Chat beta 2019-08-07 09:38:37 +02:00
Sébastien GLATZ 1c05c16041 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-07 09:35:41 +02:00
Sébastien GLATZ 62d13d66e5 Fix video bug with vimeo player 2019-08-07 09:35:33 +02:00
Sébastien GLATZ 1db0fab8e1 Add account access on desktop 2019-08-07 09:35:04 +02:00
Thomas Berrod 7e39a35997 fix quote on offre d'emploi 2019-08-06 17:20:26 +02:00
Thomas Berrod 8f97f706f0 candidature : id on Plyr 2019-08-06 16:32:51 +02:00
Sébastien GLATZ f5a6e86c61 Fix: double scrollbar bug 2019-08-06 16:27:57 +02:00
Sébastien GLATZ 9eeee67f03 Test: video vimeo 2019-08-06 16:27:30 +02:00
Sébastien GLATZ 314407806e Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-06 12:24:46 +02:00
Sébastien GLATZ 9b196e3106 Fix: display bottom bar only on mobile devices 2019-08-06 12:24:41 +02:00
Thomas Berrod c380260645 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-06 12:21:42 +02:00
Thomas Berrod 06d620ecba test mute only 2019-08-06 12:16:58 +02:00
Sébastien GLATZ ba41a79caa Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-06 12:13:27 +02:00
Sébastien GLATZ 0c443bc341 Fix: size of visible text 2019-08-06 12:13:21 +02:00
Sébastien GLATZ 9db04e1f5b Fix: image filter when card hover 2019-08-06 12:12:58 +02:00
Thomas Berrod 1be8ac03fe prepro test : autoplay off 2019-08-06 12:06:09 +02:00
Thomas Berrod dfaeb05bf4 autoplay off 2019-08-06 12:00:26 +02:00
Thomas Berrod 0b0a94e98b test preprod iphone sound issue 2019-08-06 11:56:26 +02:00
Sébastien GLATZ 8878c1c57a Fix: Plyr sound for iPhone test preprod 2019-08-06 11:48:52 +02:00
Sébastien GLATZ afe458cefd Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-06 11:12:50 +02:00
Thomas Berrod bf4fa0dd30 merge brand_name issue 2019-08-06 11:10:22 +02:00
Sébastien GLATZ 9407dab844 Little fix 2019-08-06 11:08:59 +02:00
Sébastien GLATZ e34cc49a4f Add properties for plyr video 2019-08-06 11:07:22 +02:00
Sébastien GLATZ 1de11320fd Add dark overlay on cards 2019-08-06 11:07:05 +02:00
Thomas Berrod dffd974ba6 misc fixes 2019-08-06 11:03:41 +02:00
Sébastien GLATZ 7f70c3951e Fix: sound on iPhone Plyr player 2019-08-06 10:27:41 +02:00
Sébastien GLATZ d8b66eda0d Fix: scroll to top with grid 2019-08-06 10:27:22 +02:00
Sébastien GLATZ 42ad13bb93 Fix bug with footer when change page 2019-08-05 12:17:11 +02:00
Sébastien GLATZ 1bc5a9c2c8 Add new style and pictures 2019-08-05 11:56:56 +02:00
Sébastien GLATZ c3517cdced Fix links 2019-08-05 11:56:25 +02:00
gregoire baf8c7cea0 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-02 16:38:40 +02:00
gregoire 3d4d90e46b cypress test 2019-08-02 16:38:37 +02:00
Thomas Berrod c46cddcdaf misc fixes 2019-08-02 16:24:48 +02:00
Thomas Berrod c5e631064a Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-02 16:07:31 +02:00
Thomas Berrod e5bdbe0bc8 test on offer + company data not well formed from preprod 2019-08-02 16:07:26 +02:00
gregoire 14e216d448 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-02 15:08:37 +02:00
Thomas Berrod 1a7d811e8a fix 'isInStandaloneMode' has already been declared 2019-08-02 14:46:18 +02:00
gregoire af478d49bc merge 2019-08-02 12:02:02 +02:00
Thomas Berrod 902665f0af unix case fix 2019-08-02 11:10:34 +02:00
Thomas Berrod c042ae0ef7 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-02 10:53:43 +02:00
Sébastien GLATZ 988f179f14 Add color for specific appBar 2019-08-02 10:53:10 +02:00
Sébastien GLATZ 77ad925b02 Add style 2019-08-02 10:52:56 +02:00
gregoire d1a6aef77f cypress test 2019-08-02 10:49:46 +02:00
gregoire 2daa191b90 cypress test 2019-08-02 10:47:56 +02:00
Thomas Berrod b02dcb5baf answer pseudo check 2019-08-02 10:43:12 +02:00
Sébastien GLATZ acc007ca81 Add style round avatar 2019-08-02 10:37:26 +02:00
Sébastien GLATZ 142f673a17 Add style answer card 2019-08-02 10:23:12 +02:00
Sébastien GLATZ 40725cef90 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-02 10:16:20 +02:00
Sébastien GLATZ 389d2f4fac Fix: bug on Iphone 2019-08-02 10:16:14 +02:00
Sébastien GLATZ d70d27299c Fix overriding 2019-08-02 10:15:41 +02:00
Sébastien GLATZ a6b8a7f555 Add link style 2019-08-02 10:15:22 +02:00
Sébastien GLATZ 518e5e75dc Comment part because no data 2019-08-02 10:15:09 +02:00
Sébastien GLATZ 91b056274e Correct href link 2019-08-02 10:14:47 +02:00
Thomas Berrod ce9dbdfb1c 404 2019-08-02 09:30:16 +02:00
Thomas Berrod 878335ef6d fix min help 2019-08-02 09:22:59 +02:00
Thomas Berrod 064979dd90 companies -> entreprises 2019-08-01 17:46:32 +02:00
Thomas Berrod 87d283763b fix Fragment issue 2019-08-01 15:40:28 +02:00
Thomas Berrod 5ae3750c3f Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-01 15:27:23 +02:00
Thomas Berrod ebbb380c71 fetchCurrentUserRequest in Reponse 2019-08-01 15:27:19 +02:00
gregoire f430471cd4 Merge commit class cypress 2019-08-01 15:25:48 +02:00
gregoire 4bca554c4f Cypress + class cypress 2019-08-01 15:15:08 +02:00
Sébastien GLATZ 61f547494b Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-01 14:55:46 +02:00
Sébastien GLATZ 3c74d6e809 Add footer in layout grid 2019-08-01 14:54:00 +02:00
Sébastien GLATZ 8045f38eac Little fix 2019-08-01 14:53:48 +02:00
Sébastien GLATZ 94dd549943 Add loader 2019-08-01 14:53:16 +02:00
Sébastien GLATZ ae47b6a838 Little style fix 2019-08-01 14:36:23 +02:00
Sébastien GLATZ 9de0a394d7 Add logged in icons 2019-08-01 14:35:38 +02:00
Sébastien GLATZ ce29a6438d Add newsletter and install pwa in footer 2019-08-01 14:35:21 +02:00
Sébastien GLATZ ef93e66968 Add icon 2019-08-01 14:34:23 +02:00
Sébastien GLATZ e32903baa5 Fix style 2019-08-01 14:34:05 +02:00
Thomas Berrod 753e76ccb1 misc changes 2019-08-01 12:26:42 +02:00
Thomas Berrod 7a523c50a0 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-08-01 10:41:05 +02:00
Thomas Berrod 7f5e323cd9 resetPassword 2019-08-01 10:40:59 +02:00
Sébastien GLATZ b9e6c2ad21 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-31 16:30:47 +02:00
Sébastien GLATZ b42ad17f06 Add responsive 2019-07-31 16:30:38 +02:00
Thomas Berrod ca8999a2d1 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-31 15:08:49 +02:00
Thomas Berrod bbe4a2eaa7 hooker prefetch + server config 2019-07-31 15:08:45 +02:00
Sébastien GLATZ ee6923c0a7 Little style fix 2019-07-31 15:06:46 +02:00
Sébastien GLATZ b97ff9cc7c Add NoResult component 2019-07-31 15:06:26 +02:00
Sébastien GLATZ 64f1c40594 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-31 11:52:41 +02:00
Sébastien GLATZ 13b55f50d7 Little fix 2019-07-31 11:52:31 +02:00
Sébastien GLATZ 2bbbcbae82 Add detail responsive 2019-07-31 11:52:22 +02:00
Sébastien GLATZ 0b49b1a07b Add tests on data 2019-07-31 11:51:39 +02:00
Sébastien GLATZ eb08fffb3e Update box shadow 2019-07-31 11:51:25 +02:00
Thomas Berrod 10180345d3 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-31 09:51:12 +02:00
Thomas Berrod dbeb6bac94 try cors policy 2019-07-31 09:51:07 +02:00
gregoire bdfa5d911c Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-30 16:38:37 +02:00
gregoire cb71bd841c cypress test 2019-07-30 16:38:29 +02:00
Sébastien GLATZ 3d9a4fb063 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-30 16:19:36 +02:00
Sébastien GLATZ 736b83d8d3 Add new component 2019-07-30 16:19:26 +02:00
Sébastien GLATZ 20b7ac31f4 Add pagination 2019-07-30 16:19:15 +02:00
Sébastien GLATZ 9a7f9c3993 fix responsive desktop 2019-07-30 16:19:05 +02:00
Thomas Berrod 13fb868117 url redirect fix 2019-07-30 15:39:25 +02:00
gregoire 360bd1d60f Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-30 11:41:32 +02:00
gregoire 4a75e4de1e Cypress test 2019-07-30 11:34:35 +02:00
Thomas Berrod 55247fde6b Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-30 10:25:43 +02:00
Thomas Berrod 0b2dd9a546 linkedin preprod fix 2019-07-30 10:25:37 +02:00
Sébastien GLATZ 69906acd22 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/pages/Login.js
2019-07-30 08:21:46 +02:00
Sébastien GLATZ 985ef46648 Little fix 2019-07-30 08:11:14 +02:00
Sébastien GLATZ e0af97ac41 Responsive fix 2019-07-30 08:11:02 +02:00
Thomas Berrod 73b5f427cd envelop without heart 2019-07-29 17:28:10 +02:00
Thomas Berrod 9e1449e7fa Loader tolower for unix ports 2019-07-29 17:00:36 +02:00
Thomas Berrod 007fdd2fac chat WIP 2019-07-29 16:58:52 +02:00
Thomas Berrod dc62eac5e4 login merge 2019-07-29 14:39:36 +02:00
Thomas Berrod b921d3eb46 login fix 2019-07-29 14:37:59 +02:00
gregoire c50f6fe858 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-29 12:09:55 +02:00
gregoire 7e08ee6b6f cypress test 2019-07-29 12:09:50 +02:00
gregoire 29e1c2550f cypress class 2019-07-29 12:09:33 +02:00
Thomas Berrod a96f83dca5 video back to Candidature 2019-07-26 16:56:59 +02:00
Thomas Berrod dc8687b794 merge grid 2019-07-26 16:53:28 +02:00
Thomas Berrod c216aec6c0 Candidatures + offreDetail 2019-07-26 16:50:19 +02:00
Sébastien GLATZ 97bb5d3a75 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-26 16:26:12 +02:00
Sébastien GLATZ 956deb7dc4 Responsive desktop 2019-07-26 16:26:04 +02:00
Sébastien GLATZ 512e5cf74f Update layout 2019-07-26 16:25:36 +02:00
Thomas Berrod 8951d83423 401 redirect to login 2019-07-26 15:23:23 +02:00
Thomas Berrod 3a0e9c1938 cyp tag + 2019-07-26 12:21:58 +02:00
Thomas Berrod eb27aa74ab OffreDetail Component added 2019-07-26 12:21:44 +02:00
Thomas Berrod 20bf564648 Mes candidatures WIP 2019-07-26 12:18:08 +02:00
Sébastien GLATZ bda3eb3302 Add Cypress tag for tests 2019-07-26 12:03:41 +02:00
Sébastien GLATZ 3ad44318b1 Little fix 2019-07-26 09:55:16 +02:00
Sébastien GLATZ f703a2bd8f Fix newsletter and install app responsive 2019-07-26 09:55:04 +02:00
Sébastien GLATZ 55c921c214 Fix header bottom spacing 2019-07-26 09:54:39 +02:00
Sébastien GLATZ 6188dc60a8 Fix appbar shadow 2019-07-26 09:54:10 +02:00
Sébastien GLATZ fd4bad6477 Add style for responsive 2019-07-25 16:28:41 +02:00
Sébastien GLATZ 7fa2bb1e10 Add style for responsive 2019-07-25 16:28:18 +02:00
Sébastien GLATZ 3c5b4f4f24 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-25 11:22:04 +02:00
Sébastien GLATZ dbcb06cac8 Appbar responsive 2019-07-25 11:21:57 +02:00
Sébastien GLATZ af417f1b3d Color update 2019-07-25 11:21:36 +02:00
Thomas Berrod 7d918ad5ec fix store persistance issues 2019-07-25 10:31:23 +02:00
Thomas Berrod 569f7aac9f last updates on PersistStore 2019-07-24 17:51:25 +02:00
Thomas Berrod 92df989fe3 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-24 17:02:00 +02:00
Thomas Berrod ba116d0919 Store persistance work in progress 2019-07-24 17:01:56 +02:00
Thomas Berrod 6e553077d2 Company fix 2019-07-24 17:01:06 +02:00
Sébastien GLATZ d2a87c0daa Fix button hover 2019-07-24 16:20:28 +02:00
Sébastien GLATZ abfe12415f Desktop responsive 2019-07-24 16:20:16 +02:00
Sébastien GLATZ 57dac37437 Change footer call 2019-07-24 16:19:46 +02:00
Sébastien GLATZ 4df163eb30 Add new logo region svg 2019-07-24 16:19:15 +02:00
Sébastien GLATZ 3b5bb425a1 Update style responsive 2019-07-24 13:36:47 +02:00
Sébastien GLATZ bbe9970485 Little fix 2019-07-24 08:42:17 +02:00
Sébastien GLATZ 037424f103 Fix menu inline 2019-07-24 08:42:11 +02:00
Sébastien GLATZ 6f4530c63c Add loader to horizontal scroller 2019-07-24 08:35:54 +02:00
Thomas Berrod 551b6af2d5 persist store 2019-07-23 17:26:36 +02:00
Thomas Berrod 570c8482f6 Mes candidatures start 2019-07-23 17:11:53 +02:00
Thomas Berrod fe84754a0e Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-23 16:25:47 +02:00
Thomas Berrod 8cc797f2ca Candidatures 2019-07-23 16:25:31 +02:00
Sébastien GLATZ 825e658401 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-23 16:13:06 +02:00
Sébastien GLATZ 416758c178 Import fix 2019-07-23 16:12:22 +02:00
Sébastien GLATZ 398b2ab85a Add style 2019-07-23 16:12:13 +02:00
Sébastien GLATZ 40e272ade1 Add custom loader and new component Loader 2019-07-23 16:11:47 +02:00
Sébastien GLATZ fb3809039f Little fix 2019-07-23 16:11:04 +02:00
Sébastien GLATZ 199bfb3ccb Use withWidth 2019-07-23 16:10:42 +02:00
Sébastien GLATZ 7f571b3917 Verify email + pass email to component 2019-07-23 16:09:18 +02:00
Sébastien GLATZ d4640944ee Add custom pagination 2019-07-23 16:08:29 +02:00
Thomas Berrod 61891d7cd7 menu lwhen logged in 2019-07-23 12:22:53 +02:00
Thomas Berrod 035137df0f Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-23 11:33:53 +02:00
Thomas Berrod 7354b66453 Candidature(s) ready to dev 2019-07-23 11:33:46 +02:00
Sébastien GLATZ 652d3bac27 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-23 08:12:56 +02:00
Sébastien GLATZ b6c35ba444 Style little fix 2019-07-23 08:10:50 +02:00
Sébastien GLATZ 9bb874ae2f Add inscription in url 2019-07-23 08:10:37 +02:00
Sébastien GLATZ 7ce22883d2 Add props align 2019-07-23 08:10:00 +02:00
Sébastien GLATZ 2d6210c1e7 Mail confirmation after register 2019-07-23 08:09:14 +02:00
Sébastien GLATZ 8f6c22d109 Add new layout 2019-07-23 08:07:52 +02:00
Thomas Berrod 2c4bff2e7d company ads stall 2019-07-22 11:50:43 +02:00
Sébastien GLATZ 4f66501811 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-19 16:24:16 +02:00
Sébastien GLATZ 3ad52a1e0b Bug expected string but go object 2019-07-19 16:24:02 +02:00
Thomas Berrod b3c335fb25 update user success 2019-07-19 15:20:43 +02:00
Thomas Berrod f1383d875e pending work on response 2019-07-19 12:05:47 +02:00
Sébastien GLATZ 648517180a Add back arrow on scroll up 2019-07-19 11:41:11 +02:00
Sébastien GLATZ 539e2d301f Add company detail + content collapse 2019-07-19 11:40:40 +02:00
Sébastien GLATZ dfe4a9aeef Add default avatar file 2019-07-18 10:16:00 +02:00
Sébastien GLATZ ddae8c9b82 Filter style on lists 2019-07-18 10:08:24 +02:00
Sébastien GLATZ 0f1762312f Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-18 09:52:38 +02:00
Sébastien GLATZ b22c9f7fbd Little fix 2019-07-18 09:52:12 +02:00
Thomas Berrod daeb381f8b Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-18 09:51:29 +02:00
Thomas Berrod f19d065c37 company stalling 2019-07-18 09:51:16 +02:00
Sébastien GLATZ 258ad73daa Little fix 2019-07-18 08:46:28 +02:00
Sébastien GLATZ d2eb717aab Fix logo header 2019-07-18 08:46:11 +02:00
Thomas Berrod 860b420608 fix Company saga 2019-07-17 16:47:02 +02:00
Sébastien GLATZ 3f18f70649 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/components/templates/Grid.js
2019-07-17 16:18:57 +02:00
Sébastien GLATZ b6ab0bfd2b Little fix 2019-07-17 16:18:23 +02:00
Sébastien GLATZ b17f156f38 Add request answer 2019-07-17 16:18:01 +02:00
Sébastien GLATZ 9a333d58c4 Add request answer 2019-07-17 16:17:45 +02:00
Thomas Berrod 58c34e6c47 grid aucun résultat 2019-07-17 15:44:40 +02:00
Thomas Berrod 1869fa4a07 search bar working 2019-07-17 15:36:55 +02:00
Thomas Berrod 8a16b8a47a search bar working 2019-07-17 15:34:20 +02:00
Sébastien GLATZ 212a2a1afc Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/components/templates/Grid.js
2019-07-17 15:23:46 +02:00
Sébastien GLATZ e4c2b7fe2c clean imports & styles + classes in CamelCase 2019-07-17 15:20:17 +02:00
Thomas Berrod 788c58ecad api merge 2019-07-17 14:49:36 +02:00
Thomas Berrod 4860438487 bug fix onapi fetchcompanies 2019-07-17 14:47:15 +02:00
Sébastien GLATZ ec8d55640d Bug: add company request 2019-07-17 14:40:53 +02:00
Sébastien GLATZ 1ac2510ca3 Little fix 2019-07-17 14:39:58 +02:00
Sébastien GLATZ 53a7045e43 Bug : add company request saga 2019-07-17 14:38:46 +02:00
Sébastien GLATZ b368f477ca Add layout company 2019-07-17 14:13:06 +02:00
Sébastien GLATZ 87346e9ac5 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/components/organisms/TileCompany.js
2019-07-17 09:43:15 +02:00
Sébastien GLATZ 8565f2142e Little fix provided ads 2019-07-17 09:41:52 +02:00
Thomas Berrod 24b957365f Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-17 09:37:07 +02:00
Thomas Berrod 39a86b5a62 emphasis Companies 2019-07-17 09:36:38 +02:00
Sébastien GLATZ 6febb3a654 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-16 15:40:54 +02:00
Sébastien GLATZ 409c396e68 Little fix 2019-07-16 15:40:46 +02:00
Sébastien GLATZ ecf6605eb5 Edit page help 2019-07-16 15:40:29 +02:00
Sébastien GLATZ d88c50e3bb New component SocialButtonsAuth on Login and Register 2019-07-16 15:40:11 +02:00
Sébastien GLATZ a1f8618843 Update material UI 2019-07-16 15:39:30 +02:00
Sébastien GLATZ d9820e49e0 Little fix 2019-07-16 15:39:05 +02:00
Thomas Berrod 8b876369c3 fix pourvues 2019-07-16 14:13:54 +02:00
Thomas Berrod 86aa81bdcc fix promoted 2019-07-16 11:31:12 +02:00
Thomas Berrod 23c38245a3 emphasis 2019-07-16 11:19:05 +02:00
Sébastien GLATZ 48b8851657 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/data/api.js
2019-07-16 09:13:23 +02:00
Sébastien GLATZ 2ed985518d Add offer to answer 2019-07-16 09:10:50 +02:00
Sébastien GLATZ 9d06e89f5f Bug: get promoted(pourvue) ads 2019-07-16 09:10:16 +02:00
Sébastien GLATZ 3a49f735b6 Edit page help 2019-07-16 09:09:16 +02:00
Sébastien GLATZ 69b1e087f9 Little fix 2019-07-16 09:08:34 +02:00
Thomas Berrod 980fc787bc social shar 2019-07-15 17:32:23 +02:00
Thomas Berrod 39f33690dc Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-12 17:03:40 +02:00
Thomas Berrod 0f81c9bab8 enableVideo in config 2019-07-12 17:03:27 +02:00
Sébastien GLATZ ad98b0513a Transfer data from offer to answer 2019-07-12 16:35:27 +02:00
Thomas Berrod f891479456 manifest orientation 2019-07-12 16:16:50 +02:00
Thomas Berrod fcc5bcad86 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-12 15:47:48 +02:00
Thomas Berrod 9c4364f082 snack off newsletter 2019-07-12 15:47:26 +02:00
Sébastien GLATZ ee8de0330e Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-12 15:43:40 +02:00
Sébastien GLATZ 95d906066f Little fix 2019-07-12 15:43:35 +02:00
Sébastien GLATZ b42d5abbb1 Add style to Answer 2019-07-12 15:43:21 +02:00
Sébastien GLATZ 5f886f2a10 Add RootContainer to all Layouts 2019-07-12 15:42:46 +02:00
Thomas Berrod bfcf73ae74 linkedin bug fix 2019-07-12 15:34:13 +02:00
Thomas Berrod 769d07440d pwa install 2019-07-12 15:25:19 +02:00
Thomas Berrod 435e6ad725 newsletter btn fix 2019-07-12 15:25:03 +02:00
Thomas Berrod f569ff237e manifest + PWA compliance 2019-07-12 12:04:48 +02:00
Thomas Berrod b85a4c9085 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-12 09:44:10 +02:00
Thomas Berrod b3b953196e dot env added (must yarn install) 2019-07-12 09:41:34 +02:00
Sébastien GLATZ baa0cfa8c1 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/pages/Login.js
2019-07-11 14:12:00 +02:00
Thomas Berrod 9b82a9cf59 .env 2019-07-11 14:08:02 +02:00
Sébastien GLATZ 39cd6cba2a fix .gitignore 2019-07-11 14:07:48 +02:00
Thomas Berrod 7b1aee8a44 gitignore 2019-07-11 14:07:43 +02:00
Sébastien GLATZ e04465a751 Add fg-loadcss 2019-07-11 14:07:24 +02:00
Sébastien GLATZ 09b586d94d Add style answer 2019-07-11 14:04:37 +02:00
Sébastien GLATZ 48ac5c7d2c Little fix 2019-07-11 14:04:20 +02:00
Sébastien GLATZ 43d9a05bd6 Add style for login and register 2019-07-11 14:03:55 +02:00
Sébastien GLATZ 68f44ab006 Add company list 2019-07-11 14:03:16 +02:00
Sébastien GLATZ 554c641c33 Add svg for answer 2019-07-11 14:02:49 +02:00
Sébastien GLATZ 633e89628c Add component SocialShare for offer detail 2019-07-11 14:01:59 +02:00
Sébastien GLATZ 5012b1e97a Update theme 2019-07-11 14:00:59 +02:00
Thomas Berrod 6d72c814d0 server url global 2019-07-11 11:48:08 +02:00
Thomas Berrod 57d1e641a5 ecosystem 2019-07-10 17:55:57 +02:00
Thomas Berrod 0b392222cf .env exclu 2019-07-10 17:45:43 +02:00
Thomas Berrod e05f5c2b8f mangling 2019-07-10 17:44:47 +02:00
Thomas Berrod 9a3bd880fb prod switch 2019-07-10 17:40:42 +02:00
Thomas Berrod 07b5e79d2b KeyboardArrowLeft uppercase 2019-07-09 17:37:09 +02:00
Thomas Berrod c1e3c17504 snack case fix 2019-07-09 17:31:26 +02:00
Thomas Berrod 354fefdae6 case sensitivness 2019-07-09 17:28:23 +02:00
Thomas Berrod ba4aba6a6d preprod directives 2019-07-09 15:36:45 +02:00
Thomas Berrod f392062ece variables globales 2019-07-09 14:17:33 +02:00
Thomas Berrod 927eb81017 fix login 2019-07-08 18:47:20 +02:00
Thomas Berrod ee4ef59ed3 fix forms login error message 2019-07-08 18:04:23 +02:00
Sébastien GLATZ 063fb19dbd Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-08 09:58:43 +02:00
Sébastien GLATZ 9169ee38ad Update detail offer 2019-07-08 09:58:37 +02:00
Sébastien GLATZ e68b61ce35 Little fix 2019-07-08 09:58:22 +02:00
Thomas Berrod 1f901b9a0a insight : Plyr 404 on .json to load poster image 2019-07-08 09:50:21 +02:00
Thomas Berrod 3abfff129c Icontext proptypes fix 2019-07-08 09:40:13 +02:00
Thomas Berrod 1267aa15b1 flush ttJWT when expired 2019-07-05 17:22:48 +02:00
Sébastien GLATZ 60e22554a8 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-05 15:33:16 +02:00
Sébastien GLATZ 72d98e057c Little fix 2019-07-05 15:33:10 +02:00
Sébastien GLATZ 0706fda67c Add offer list and offer detail 2019-07-05 15:32:37 +02:00
Thomas Berrod 1bdbd2f77d warning fixes 2019-07-05 15:31:37 +02:00
Thomas Berrod 4daa99a020 displayName fix 2019-07-05 09:47:45 +02:00
Sébastien GLATZ 89221a04ed Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-05 08:14:48 +02:00
Sébastien GLATZ 765075e99b Update home and navigation style + footer 2019-07-05 08:14:42 +02:00
Thomas Berrod 245f09b20f linkedin + facebook stalling 2019-07-04 17:20:43 +02:00
Thomas Berrod d2649fdc16 litlle bugs 2019-07-03 17:24:29 +02:00
Thomas Berrod 2c08e9e68b snack + newsletter 2019-07-03 17:10:14 +02:00
Sébastien GLATZ 5009409a51 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga
# Conflicts:
#	nextjs/reducers/user.js
2019-07-03 16:43:27 +02:00
Sébastien GLATZ a8e4e739a2 Home video 2019-07-03 16:41:16 +02:00
Sébastien GLATZ e1579a9d1d Newsletter 2019-07-03 16:40:58 +02:00
Thomas Berrod 21510e7b71 register loop + menage 2019-07-03 14:44:35 +02:00
Sébastien GLATZ 59782cda43 Merge branch 'Saga' of https://git.e-declic.net/talents-tube/app into Saga 2019-07-03 08:24:28 +02:00
Sébastien GLATZ 6947ba6b33 Update parameters 2019-07-03 08:24:19 +02:00
Sébastien GLATZ 71ad5427cb Add style for home video 2019-07-03 08:23:44 +02:00
Sébastien GLATZ c14de8e119 Add style on navigation 2019-07-03 08:23:25 +02:00
Sébastien GLATZ 7e67bdcb45 Update theme colors 2019-07-03 08:23:04 +02:00
Thomas Berrod 5a0267e20f register forms + confirm email + verify email 2019-07-02 17:13:14 +02:00
Sébastien GLATZ aa3ef3456d Integration home page horizontal scroller and news 2019-06-28 15:30:24 +02:00
Sébastien GLATZ 03c01d842d Update general style 2019-06-28 15:28:48 +02:00
Sébastien GLATZ 502490ced1 Update layout height without footer 2019-06-28 15:27:29 +02:00
198 changed files with 61259 additions and 22241 deletions
-3
View File
@@ -1,3 +0,0 @@
nextjs/node_modules/
/node_modules/
/nextjs/node_modules/
+3
View File
@@ -0,0 +1,3 @@
{
"git.ignoreLimitWarning": true
}
+1
View File
@@ -0,0 +1 @@
/volume/
+50
View File
@@ -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)
+28
View File
@@ -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.
+17
View File
@@ -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:
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": {
"cypress": "^3.4.1"
}
}
+1217
View File
File diff suppressed because it is too large Load Diff
-25
View File
@@ -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
View File
@@ -1,4 +1,8 @@
/node_modules/
.next/
.gitignore
.env
ecosystem.config.js
yarn-error.log
robots.txt
/out/
yarn.lock
+18
View File
@@ -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
+43
View File
@@ -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
View File
@@ -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 lapplication 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
View File
@@ -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
});
+94
View File
@@ -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
View File
@@ -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
});
+23
View File
@@ -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
View File
@@ -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
})
+18
View File
@@ -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);
+84
View File
@@ -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>
);
}
+36 -14
View File
@@ -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);
+54
View File
@@ -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);
+113
View File
@@ -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);
+36
View File
@@ -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)
+33
View File
@@ -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
+51
View File
@@ -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);
+131
View File
@@ -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>
);
}
+29
View File
@@ -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
}
+18
View File
@@ -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
+55
View File
@@ -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: "",
};
+176
View File
@@ -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);
+138
View File
@@ -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
+106
View File
@@ -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);
+164 -9
View File
@@ -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',
},
}
},
},
});
+328
View File
@@ -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))
+263
View File
@@ -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 dinformations"]
,
["CONTACTED"
, "Demande dinformations"]
,
["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>
);
}
+150
View File
@@ -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)
+113 -88
View File
@@ -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>
);
}
}
+262 -7
View File
@@ -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;
+518 -173
View File
@@ -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))));
+103 -20
View File
@@ -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)));
+471
View File
@@ -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));
+120 -81
View File
@@ -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>
);
}
}
+99 -55
View File
@@ -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>
}
}
+58 -27
View File
@@ -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>
);
+167 -79
View File
@@ -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
}
};
}
+133 -54
View File
@@ -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 dimpression', 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>
);
}
+62 -55
View File
@@ -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);
+46 -27
View File
@@ -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);
+46 -27
View File
@@ -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>
);
-24
View File
@@ -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)
+225 -48
View File
@@ -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);
+10 -28
View File
@@ -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>
)
}
+93
View File
@@ -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))
+20 -18
View File
@@ -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)
+80 -46
View File
@@ -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))
+24 -97
View File
@@ -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)
+10
View File
@@ -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()
})
})
+1
View File
@@ -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"]}}
+1
View File
@@ -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}
+52
View File
@@ -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
Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

+46
View File
@@ -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)
})
})
+31
View File
@@ -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
View File
@@ -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}`)
},
}
};
+1 -1
View File
@@ -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
+6 -9
View File
@@ -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
View File
@@ -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
}
},
+28
View File
@@ -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
View File
@@ -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
View File
@@ -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));
+228
View File
@@ -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))
+32 -17
View File
@@ -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))
+499
View File
@@ -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 lUtilisateur sur la Plateforme lui permettant notamment de présenter son profil, de consulter, de partager, de valoriser du Contenu Utilisateur et daccéder à certains Services.
« Conditions Générales dUtilisation et de Vente » ou « CGUV » : désigne les présentes conditions générales dutilisation 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 daccéder aux Services et disponible à ladresse 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 dun 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 DAPPLICATION
</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 daccéder à la Plateforme et les conditions de délivrance des Services qui y sont proposés.
Les présentes CGUV sappliquent sans restriction ni réserve à toute utilisation de la Plateforme et des Services y associés.
LUtilisateur 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.
LUtilisateur 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 dun CV vidéo par les Talents ;
Le dépôt en ligne dune offre demploi en vidéo par les Entreprises ;
La mise en avant dune vidéo Territoire ;
Laccès le cas échéant à un CV Vidéothèque ;
La création dune page dédiée sous la forme dun mini-site pour lEntreprise ;
Un service personnalisé de coaching, de tournage et de montage de CV vidéos. Pour ce Service, lUtilisateur 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 dabonnements et/ou de crédits utilisables directement sur la Plateforme. La description des différentes offres et formules figure sur la Plateforme à longlet « Services TALENTS TUBE ».
Afin de réaliser la commande et/ou souscription à lun 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 larticle 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 à longlet « Tarifs » de la Plateforme.
Les prix des Services payants sont indiqués en euros et sentendent 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 lUtilisateur 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 à lUtilisateur 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 lobjet dun 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 lidentité de lUtilisateur payeur et autorise le paiement. Le système didentification étant spécifique à chaque banque, lUtilisateur doit vérifier auprès de sa banque le procédé dauthentification utilisé. LUtilisateur doit sassurer é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 à lUtilisateur.
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 DUN COMPTE UTILISATEUR
</Typography>
<Typography className={classes.subtitle} component="subtitle1" gutterBottom align="center">
5.1. CONDITIONS DACCES ET DINSCRIPTION
</Typography>
<Typography className={classes.text} gutterBottom align="center">
{
`
Laccès à la Plateforme et lutilisation 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.
LUtilisateur sengage à 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.
LUtilisateur sinterdit 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 DUN COMPTE UTILISATEUR
</Typography>
<Typography className={classes.text} gutterBottom align="center">
{
`
Pour bénéficier de certains Services, lUtilisateur est invité à créer au préalable un Compte Utilisateur sur la Plateforme.
En accédant aux Services, lUtilisateur reconnaît avoir reçu préalablement à la création de son Compte Utilisateur, une information claire et compréhensible notamment sur lidentité de la société TALENTS TUBE et sur les caractéristiques essentielles des Services proposés. LUtilisateur 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, lensemble des champs obligatoires figurant sur le formulaire dinscription doivent être complétés. Lidentifiant et le mot de passe du Compte Utilisateur sont strictement personnels et confidentiels. LUtilisateur s'engage à garantir la confidentialité de lidentifiant et du mot de passe choisis lors de la création du Compte Utilisateur et sinterdit de les communiquer à des Tiers. LUtilisateur s'engage à ne pas créer ou utiliser, sous sa propre identité ou celle dun Tiers, dautres 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 lUTILISATEUR
</Typography>
<Typography className={classes.text} gutterBottom align="center">
{
`
L'Utilisateur s'engage à fournir des informations personnelles exactes et conformes et à les mettre à jour afin den garantir la pertinence et lexactitude tout au long de lutilisation 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 sagit 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 lexactitude 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 longlet prévu à cet effet. LUtilisateur demeure responsable de toutes utilisations faites de la Plateforme sous ses identifiants jusqu'à la fermeture effective de son Compte Utilisateur.
La fermeture dun Compte Utilisateur entraîne la résiliation automatique des Services commandés et/ou souscrits dans les conditions de larticle 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 lUtilisateur ne seront cependant plus accessibles sur la Plateforme.
Le non-respect par lUtilisateur des présentes CGUV et/ou lusage illicite et/ou abusif des Services et/ou de la Plateforme, entraînera le droit pour TALENTS TUBE, dinterrompre, de suspendre ou de supprimer laccè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 DUTILISATION 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, lUtilisateur doit disposer dun ordinateur PC ou MAC et/ou dun appareil mobile de type smartphone et/ou dune tablette sous système dexploitation et navigateur récents. Avant toute inscription sur la Plateforme, lUtilisateur sassure quil dispose dun é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.
LUtilisateur 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 quil 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 dautres 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 nayant pas souscrit aux Services concernés.
LUtilisateur sengage à agir de manière professionnelle et sinterdit 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 dauteur, 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 DUTILISATION 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 sinterdit notamment :
Dutiliser la Plateforme et les Services pour distribuer des courriers électroniques non sollicités ou des publicités ;
Dharceler tout autre Utilisateur ;
De collecter ou de stocker toute donnée à caractère personnel appartenant à un autre Utilisateur pour dautres raisons que des échanges dinformations 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">
{
`
LUtilisateur 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 larticle 6. I. 7 de la loi pour la confiance dans l’économie numérique du 21 juin 2004 (n°2004-575), lUtilisateur 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 dun courriel adressé via le formulaire de contact prévu à cet effet dans lequel lUtilisateur déclinera ses noms, prénoms, adresse et la description des faits litigieux.
Formulaire de contact : Cliquez ici.
Dans la mesure du possible, lUtilisateur 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é.
Lattention de lUtilisateur, auteur dune notification, est attirée sur le fait quune dénonciation inexacte est susceptible de lexposer à des sanctions pénales et accepte de garantir TALENTS TUBE des conséquences éventuelles dune notification abusive.
TALENTS TUBE sengage à réagir promptement à toute notification dun Utilisateur faite dans les formes et à première vue fondée.
TALENTS TUBE veillera à préserver le cas échéant lanonymat de lUtilisateur, 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 dinterrompre ponctuellement laccès aux Services pour assurer la sécurité et la maintenance. TALENTS TUBE en informera lUtilisateur par courrier électronique, par avertissement visible sur la page daccueil de la Plateforme ou tout autre moyen de communication.
LUtilisateur peut informer TALENTS TUBE à tout moment, de toute difficulté rencontrée au cours de lutilisation des Services. À cet effet, TALENTS TUBE met à la disposition de lUtilisateur une adresse de contact lui permettant de communiquer avec le support technique.
Contact technique : webmaster@talentstube.com.
LUtilisateur sinterdit 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">
{
`
LUtilisateur 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.
LUtilisateur peut exercer ce droit en remplissant le formulaire annexé aux présentes et en ladressant par e-mail à contact@talentstube.com.
Conformément à larticle L. 221-24 du code de la consommation TALENTS TUBE sengage à rembourser lUtilisateur dans les quatorze (14) jours à compter de lannulation de la commande et/ou souscription.
TALENTS TUBE procède au remboursement en utilisant le même moyen de paiement que celui utilisé par lUtilisateur lors de la commande, sauf accord exprès de lUtilisateur pour le recours à un autre moyen de paiement, et dans la mesure le remboursement n'occasionne pas de frais pour lUtilisateur.
LUtilisateur reconnaît quen 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, lUtilisateur sera invité à renoncer expressément à son droit de rétractation.
Le présent droit de rétractation nest 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">
{
`
LUtilisateur 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, lUtilisateur 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 labonnement.
En cas de manquement simple de lUtilisateur à 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 lenvoi dune notification par courrier ou par e-mail restée infructueuse. En cas de manquement grave de lUtilisateur, 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 à lUtilisateur 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 dun 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 daccord 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 dauteur, droit à limage etc.) sont consentis pour toute la durée dutilisation 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 lUtilisateur, destinées exclusivement à lutilisation des Services et qui font lobjet dun traitement automatisé à des fins techniques et statistiques dans le cadre de la gestion de la Plateforme, des Comptes Utilisateurs ainsi que de lamé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 lUtilisateur ne sont en aucun cas transmises à des Tiers à lexclusion 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 lamé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, lUtilisateur dispose dun droit daccès, de rectification et de suppression portant sur les données personnelles le concernant.
LUtilisateur 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 lordinateur de lUtilisateur 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 lUtilisateur par le biais dun identifiant unique.
Pour la gestion du contenu de sa Plateforme, TALENTS TUBE dépose des cookies sur le terminal de lUtilisateur.
Les cookies utilisés ont pour finalité :
La mémorisation des identifiants de session ;
Lauthentification de lUtilisateur.
Conformément à larticle 32-II de la loi du 6 janvier 1978, TALENTS TUBE informe lUtilisateur par un bandeau que la poursuite de sa navigation vaut accord pour linstallation 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 lUtilisateur, etc.). Ils ne feront pas lobjet dun recueil de consentement préalable.
Néanmoins, lUtilisateur peut refuser linstallation 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 daudience ou pour générer des partages de réseaux sociaux. Dans de telles hypothèses, TALENTS TUBE recueille expressément le consentement de lUtilisateur, 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 lexpiration de ce délai, TALENTS TUBE recueille de nouveau, quand il est nécessaire, le consentement de lUtilisateur.
`
}
</Typography>
<Typography className={classes.title} component="h2" gutterBottom align="center">
ARTICLE 11 - CONFIDENTIALITE
</Typography>
<Typography className={classes.text} gutterBottom align="center">
{
`
TALENTS TUBE sengage à mettre en œuvre des moyens raisonnables pour assurer la sécurité et la confidentialité des données échangées au titre de lutilisation 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 lhébergeur pour tout Contenu Utilisateur qui ne fait pas lobjet dun 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 lensemble des obligations à sa charge, TALENTS TUBE est tenue dune obligation de moyens.
TALENTS TUBE ne saurait être tenue responsable des dommages, directs ou indirects, qui pourraient survenir en raison de lutilisation 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 lincapacité 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 lUtilisateur.
TALENTS TUBE ne garantit ni laptitude 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 lEntreprise du fait du recrutement ou de lengagement dun Talent. À cet égard, lEntreprise 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 lexactitude 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 nindique 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é à lutilisation 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.
LUtilisateur est responsable de lutilisation 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, lUtilisateur sengage à 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 laccè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 lUtilisateur ;
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.
LUtilisateur et TALENTS TUBE sefforceront de résoudre à lamiable les contestations qui pourraient surgir relatives à la validité, linterprétation ou lexécution des présentes.
À défaut daccord amiable, lUtilisateur a la possibilité de recourir à une procédure de médiation conventionnelle ou à tout autre mode alternatif de règlement des différends.
Si lUtilisateur est un professionnel, les tribunaux du ressort de la Cour dAppel de RENNES seront seuls compétents pour connaître de tout litige relatif à lexécution et/ou à linterprétation des CGUV, y compris en cas dappel en garantie et de pluralité de défendeurs.
`
}
</Typography>
</div>
</LayoutBack>
)
}
}
export default withStyles(styles)(Cgu);
-66
View File
@@ -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));
-77
View File
@@ -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);
+362
View File
@@ -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, dune manière générale, sur la collecte et le traitement de vos informations personnelles par la société talentstube.com, nhé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.
Cest 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 à dautres fins. Nos finalités sont en adéquation avec les normes de conformité de la Cnil (Commission nationale de linformatique et des libertés).
À titre dexemple, 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 lobjectif :
« 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 dexprimer 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 didentité
`
}
</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 dautres 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 sengage à 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, dopposition et de portabilité des données. Nous disposons dun 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. Lexercice 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 sagit 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 dutiliser 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 dinformations 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 lorganisme 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 danalyse web Google Analytics. Cela nous permet danalyser 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 dun internaute sur le site. Ces cookies sont conservés pour une durée de 26 mois.
Nous avons activé lanonymisation des adresses IP pour ce site. Cela signifie que les adresses IP collectées par Google Analytics sont systématiquement raccourcies pour rendre impossible lidentification des internautes. Dans certains cas exceptionnels, ladresse 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 danalyser les habitudes de navigation des internautes et de fournir des rapports sur lactivité du site.
Google met à disposition des utilisateurs un plug-in pour leur navigateur leur permettant dempê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 dannonces de Google. Cela nous permet notamment dutiliser vos données de navigation sur ce site afin de vous proposer des annonces ciblées lors que vous visitez dautres 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 dutiliser 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 safficher sur ces autres appareils. Bien que Google collecte sur ce site des données liées à lutilisation de comptes Google, talents tube ne peut en aucun cas en déduire lidentité des utilisateurs de comptes Google consultant le site.
Pour plus dinformations 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);
+126
View File
@@ -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));
+50
View File
@@ -0,0 +1,50 @@
import React from 'react'
import PropTypes from 'prop-types';
import LayoutBack from '../components/templates/LayoutBack'
import { withStyles } from '@material-ui/core/styles'
import Head from 'next/head'
import _ from 'lodash'
import VideoVimeo from '../components/atoms/VideoVimeo'
const styles = theme => ({
container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'flex-start',
padding: theme.spacing(3,2),
width: '100%',
},
vimeoVideo: {
width: '100% !important',
},
});
class Conseils extends React.Component {
render() {
const { classes } = this.props
return (
<LayoutBack text="Quelques conseils !">
<Head>
<title>Quelques conseils !</title>
<meta name="description" content="Quelques conseils !" />
<meta key="robots" name="robots" content="noindex, nofollow"></meta>
<link rel="canonical" href="https://app.talentstube.com" />
</Head>
<section className={ classes.container }>
<VideoVimeo videoId='356411947' autoplay='1'/>
</section>
</LayoutBack>
)
}
}
Conseils.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(Conseils);

Some files were not shown because too many files have changed in this diff Show More