Compare commits

..

2 Commits

Author SHA1 Message Date
Huaoe 7d70b308cf gitignore 2019-09-17 11:29:36 +02:00
Huaoe 077df03406 after merge 2019-09-17 11:28:32 +02:00
200 changed files with 22237 additions and 61219 deletions
+4
View File
@@ -0,0 +1,4 @@
/nextjs/node_modules/
/node_modules/
/nextjs/node_modules/
/jenkins/
-3
View File
@@ -1,3 +0,0 @@
{
"git.ignoreLimitWarning": true
}
-1
View File
@@ -1 +0,0 @@
/volume/
-50
View File
@@ -1,50 +0,0 @@
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
@@ -1,28 +0,0 @@
## 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
@@ -1,17 +0,0 @@
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
@@ -1,5 +0,0 @@
{
"dependencies": {
"cypress": "^3.4.1"
}
}
-1217
View File
File diff suppressed because it is too large Load Diff
+25
View File
@@ -0,0 +1,25 @@
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
+1 -6
View File
@@ -1,9 +1,4 @@
/node_modules/
.next/
.env
ecosystem.config.js
yarn-error.log
robots.txt
/out/
yarn.lock
.gitignore
-18
View File
@@ -1,18 +0,0 @@
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 build && pm2 startOrReload ecosystem.config.js --env production --no-daemon
-43
View File
@@ -1,43 +0,0 @@
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'
}
}
}
}
+1 -30
View File
@@ -2,7 +2,7 @@
## How to use
Download the example [or clone the repo](https://git.e-declic.net/talents-tube/app.git):
Download the example [or clone the repo](https://github.com/mui-org/material-ui):
```bash
git clone
@@ -12,7 +12,6 @@ Install it and run:
```bash
yarn install
yarn build
yarn dev
```
@@ -25,31 +24,3 @@ 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
## 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
+9 -29
View File
@@ -5,26 +5,11 @@ import {
FETCH_AD_REQUEST,
FETCH_AD_SUCCESS,
FETCH_AD_FAILURE,
FETCH_EMPHASIS_ADS_REQUEST,
FETCH_EMPHASIS_ADS_SUCCESS,
FETCH_EMPHASIS_ADS_FAILURE,
CLEAR_AD,
COUNT_AD_VIEW,
TOGGLE_PROMOTED_FILTER_ADS
FETCH_PROMOTED_ADS_REQUEST,
FETCH_PROMOTED_ADS_SUCCESS,
FETCH_PROMOTED_ADS_FAILURE,
} 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
@@ -45,18 +30,18 @@ export const fetchAdsRequest = (payload) => ({
//Ads list
export const fetchEmphasisAdsRequest = (payload) => ({
type: FETCH_EMPHASIS_ADS_REQUEST,
export const fetchPromotedAdsRequest = (payload) => ({
type: FETCH_PROMOTED_ADS_REQUEST,
payload
});
export const fetchEmphasisAdsSuccess = res => ({
type: FETCH_EMPHASIS_ADS_SUCCESS,
export const fetchPromotedAdsSuccess = res => ({
type: FETCH_PROMOTED_ADS_SUCCESS,
res
});
export const fetchEmphasisAdsFailure = res => ({
type: FETCH_EMPHASIS_ADS_FAILURE,
export const fetchPromotedAdsFailure = res => ({
type: FETCH_PROMOTED_ADS_FAILURE,
res
});
@@ -75,9 +60,4 @@ 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
@@ -1,94 +0,0 @@
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
});
+13 -64
View File
@@ -2,73 +2,22 @@ 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
});
//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
});
type: FETCH_COMPANIES_REQUEST,
payload
});
export const fetchCompaniesSuccess = res => ({
type: FETCH_COMPANIES_SUCCESS,
res
});
export const fetchCompaniesFailure = res => ({
type: FETCH_COMPANIES_FAILURE,
res
});
-23
View File
@@ -1,23 +0,0 @@
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
});
+4 -127
View File
@@ -7,45 +7,8 @@ import {
SUBSCRIBE_NEWSLETTER_FAILURE,
REQUEST_REGISTER,
REQUEST_REGISTER_SUCCESS,
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
REQUEST_REGISTER_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
});
@@ -67,7 +30,7 @@ export const registerUserRequest = (payload) => ({
export const registerUserSuccess = () => ({
type: REQUEST_REGISTER_SUCCESS,
});
export const registerUserFailure = (err) => ({
@@ -75,20 +38,6 @@ 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
@@ -98,78 +47,6 @@ export const subscribeToNewsletterSuccess = () => ({
type: SUBSCRIBE_NEWSLETTER_SUCCESS,
});
export const subscribeToNewsletterFailure = (err) => ({
export const subscribeToNewsletterFailure = () => ({
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
@@ -1,18 +0,0 @@
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
@@ -1,84 +0,0 @@
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>
)
}
@@ -1,94 +0,0 @@
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>
)
}
@@ -1,54 +0,0 @@
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>
);
}
+14 -36
View File
@@ -1,50 +1,29 @@
import React from 'react';
import React, { Fragment } from 'react';
import PropTypes from "prop-types";
import { withStyles } from '@material-ui/core/styles';
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';
import SvgIcon from '@material-ui/core/SvgIcon';
const variantIcon = {
assignment: AssignmentIcon,
room: RoomIcon,
location_on: LocationOnIcon,
business_center: BusinessCenterIcon,
};
const styles = theme => ({
root: {
alignItems: 'flex-start',
justifyContent: 'center',
marginLeft: theme.spacing(3),
marginRight: theme.spacing(3),
},
icon: {
fontSize: '1.1em',
},
text: {
fontSize: '0.9em',
fontWeight: '200',
card: {
display: 'flex',
position: 'relative',
height: '60vh',
width: '100%',
},
});
class IconText extends React.Component {
render() {
const { classes, variant, text } = this.props;
const Icon = variantIcon[variant];
return (
<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>
<Fragment>
<SvgIcon>
<path d="M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z" />
</SvgIcon>
</Fragment>
);
}
}
@@ -52,7 +31,6 @@ 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
@@ -1,54 +0,0 @@
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);
-115
View File
@@ -1,115 +0,0 @@
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';
import Link from 'next/link'
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
@@ -1,36 +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';
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
@@ -1,33 +0,0 @@
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
@@ -1,51 +0,0 @@
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
@@ -1,131 +0,0 @@
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>
);
}
@@ -1,103 +0,0 @@
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
@@ -1,29 +0,0 @@
'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
@@ -1,18 +0,0 @@
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
@@ -1,55 +0,0 @@
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: "",
};
-178
View File
@@ -1,178 +0,0 @@
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) {
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.isUserSubscribedToPush
};
}
export default connect(mapStateToProps, { subscribeUserToPushRequest })(WebPush);
-138
View File
@@ -1,138 +0,0 @@
//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
@@ -1,106 +0,0 @@
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);
+7 -162
View File
@@ -3,185 +3,30 @@ import red from '@material-ui/core/colors/red';
// Create a theme instance.
const theme = createMuiTheme({
// COLORS
palette: {
primary: {
main: '#1689FB',
darkMain: '#155EE4',
dark: '#1E2327',
important: '#25D89A',
conditional: '#D2D3D3',
main: '#155EE4',
},
secondary: {
main: '#FFFFFF',
dark: '#BBBCBD',
important: '#FF5A5F',
text: '#787B7D',
main: '#19857b',
},
flash: {
main: '#25D89A',
},
error: {
main: red.A400,
},
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',
default: '#fff',
},
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: {
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)',
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)',
},
},
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
@@ -1,328 +0,0 @@
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
@@ -1,263 +0,0 @@
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);
@@ -1,39 +0,0 @@
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
@@ -1,150 +0,0 @@
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)
+82 -107
View File
@@ -6,12 +6,10 @@ 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()
@@ -19,57 +17,40 @@ const styles = theme => ({
card: {
display: 'flex',
position: 'relative',
height: '67vh',
height: '50vh',
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)',
},
},
[theme.breakpoints.up('md')]: {
width: '30%',
height: '53vh',
width : '30%',
},
[theme.breakpoints.down('md')]: {
width: '40%',
height: '67vh',
width : '40%',
},
[theme.breakpoints.down('xs')]: {
width: '100%',
width : '100%',
},
margin: theme.spacing(1.5),
marginBottom: theme.spacing(3),
margin: '1vh 2vh 1vh 2vh',
[theme.breakpoints.up('sm')]: {
margin: theme.spacing(2, 0),
margin: '1vh 0 1vh 0',
},
borderRadius: '4px',
borderRadius: 0
},
detailsContainer: {
details_container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
width: '100%',
paddingTop: theme.spacing(6),
padding: '8vh 0 0 0',
},
companyTitle: {
textAlign: 'center',
company_title: {
fontSize: '1em',
fontWeight: '500',
minHeight: '5vh',
padding: theme.spacing(0,1.5),
fontWeight: 'normal',
textTransform: 'uppercase',
},
content: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
flexWrap: 'wrap',
width: '100%',
padding: 0,
@@ -79,108 +60,102 @@ 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',
},
logoContainer: {
logo_container: {
display: 'flex',
alignItems: 'center',
width: 85,
height: 85,
width: 100,
height: 100,
position: 'absolute',
left: '50%',
top: '50%',
transform: 'translate(-50%, -66%)',
border: 'solid 5px white',
backgroundColor: 'white',
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)',
boxShadow: '0 8px 6px -6px grey',
},
logo: {
width: '100%',
height: 'auto',
objectFit: 'contain',
borderRadius: '0px',
objectfit: 'contain',
borderRadius: 0,
},
chip: {
margin: '0 1vh 0 1vh',
}
});
class CompanyRowComponent extends React.Component {
render() {
const { classes, theme, rowData } = this.props;
const imgDefaultUrl = "/static/images/default/default_picture_company.svg"
const imgDefaultUrl = "https://www.talentstube.com/img/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.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 logoUrl = publicRuntimeConfig.logoRootUrl + rowData.logo
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 (
<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>
<Card onClick={() => Router.push(`/Company?id=${rowData.id}`)}>
<CardContent className={classes.content}>
<CardMedia
className={classes.cover}
image={imgUrl}
title={rowData.title}
/>
<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>
<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>
);
}
}
+7 -262
View File
@@ -1,277 +1,22 @@
import Link from 'next/link'
import { withStyles } from '@material-ui/core/styles'
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
});
import { Fragment, Children } from 'react';
import { Button } from '@material-ui/core';
const styles = theme => ({
root: {
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',
},
position: 'relative',
height: '10vh'
},
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 (
<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>
<Fragment>
{props.children}
</Fragment>
<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>
);
}
@@ -1,8 +0,0 @@
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;
@@ -1,8 +0,0 @@
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;
+177 -506
View File
@@ -1,55 +1,43 @@
import React, { Fragment, Children } from 'react';
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
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 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 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 List from '@material-ui/core/List';
import ListItem from '@material-ui/core/ListItem';
import { ListItemText } from '@material-ui/core';
import Loader from './../templates/loader'
import CircularProgress from '@material-ui/core/CircularProgress';
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";
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,
@@ -58,234 +46,38 @@ const styles = theme => ({
menuLogo: {
cursor: 'pointer',
[theme.breakpoints.down('md')]: {
width: '200px'
width:'200px'
},
[theme.breakpoints.down('sm')]: {
width: '130px'
width:'100px'
},
},
menuAvatar: {
width: '2.5em',
borderRadius: '50%',
marginTop: theme.spacing(2),
marginLeft: theme.spacing(2),
},
menuUserName: {
color: theme.palette.menu.emailText,
fontSize: '1.3rem',
fontWeight: '500',
},
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',
title: {
display: 'none',
[theme.breakpoints.up('sm')]: {
display: 'block',
},
[theme.breakpoints.down('md')]: {
display: 'none',
},
marginRight: 15,
cursor: 'pointer'
},
sectionDesktop: {
display: 'none',
[theme.breakpoints.up('md')]: {
'&>a:first-child': {
height: '64px',
},
'&>a:first-child li': {
height: '64px',
alignItems: 'center',
},
display: 'flex',
},
},
sectionMobile: {
display: 'flex',
[theme.breakpoints.up('md')]: {
display: 'none',
},
},
fullList: {
width: 'auto',
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,
},
cursor: 'pointer'
},
});
@@ -299,7 +91,7 @@ class Header extends React.Component {
};
componentDidMount() {
}
toggleDrawer = (side, open) => () => {
@@ -312,8 +104,6 @@ class Header extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<Drawer anchor="left" open={this.state.left} onClose={this.toggleDrawer('left', false)}>
@@ -323,78 +113,75 @@ class Header extends React.Component {
onClick={this.toggleDrawer('left', false)}
onKeyDown={this.toggleDrawer('left', false)}
>
<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} />
<div className={classes.fullList}>
<List>
<Link href="/" prefetch >
<a><ListItem>
<img alt="talents-tube" src='/static/images/logos/Logo-header.svg' className={classes.menuLogo} />
</ListItem></a>
</Link>
<Divider />
<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>
<Link href="/" 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='Accueil'>
</ListItemText>
</ListItem></a>
</Link>
<Link href="/Offres" prefetch >
<a><ListItem>
</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>
</List>
</div>
</div>
</Drawer>
@@ -406,234 +193,117 @@ class Header extends React.Component {
onClick={this.toggleDrawer('right', false)}
onKeyDown={this.toggleDrawer('right', false)}
>{!!this.props.loading &&
(<Loader />
(<div className="alert alert-info"><CircularProgress color="secondary" />Chargement...</div>
)}
{!!!this.props.loading && !!!this.props.fetched
&&
<div className={classes.fullList + ' ' + classes.darkFullList}>
<div className={classes.fullList}>
<List>
<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>
</ListItem>
<ListItem>
</ListItem>
<ListItem>
</ListItem>
<ListItem>
</ListItem>
<Divider />
<Link href="/login" prefetch >
<a><ListItem>
<ListItemText primary='Connexion'>
</ListItemText>
</ListItem></a>
</Link>
{/* TODO */}
<Link href="/login" prefetch >
<a><ListItem>
<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 primary='Inscription'>
</ListItemText>
</ListItem>
</a>
</div>
</ListItem></a>
</Link>
<Divider />
<Link href="https://www.talentstube.com/faire-offre-emploi-video" >
<a><ListItem>
<ListItemText primary='Espace recruteurs'>
</ListItemText>
</ListItem></a>
</Link>
</List>
</div>}
{!!!this.props.loading && !!this.props.fetched &&
<Fragment>
{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>
{this.props.user.firstname}
{this.props.user.lastname}
{this.props.user.email}
</ListItem>
<Divider className={classes.darkDivider} />
<Divider />
{/* TODO:real links */}
<Link href="/Candidatures" >
<a className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
<FolderSharedIcon className={classes.icon} />
<Link href="/Candidatures" prefetch >
<a><ListItem>
<ListItemText primary='Mes candidatures'>
</ListItemText>
</ListItem></a>
</Link>
<Link href="https://www.talentstube.com/login" >
<a className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
<AccountCircleIcon className={classes.icon} />
<Link href="/help" prefetch >
<a><ListItem>
<ListItemText primary='Mon compte'>
</ListItemText>
</ListItem></a>
</Link>
<Link href="/Parametres" >
<a className={classes.lightMenuItem}><ListItem classes={{ root: classes.rootListItemIcon }}>
<SettingsIcon className={classes.icon} />
<Link href="/help" prefetch >
<a><ListItem>
<ListItemText primary='Paramètres'>
</ListItemText>
</ListItem></a>
</Link>
<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>
<ListItem>
<Button
variant="contained"
color="primary"
className={classes.button}
onClick={() => this.props.requestLogout()}>Déconnexion</Button>
</ListItem>
</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} />
{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>
<img alt="talents-tube" src='/static/images/logos/Logo-header.svg' className={classes.menuLogo} />
<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 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>
</div >
);
}
}
Header.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
user: state.user,
@@ -641,4 +311,5 @@ function mapStateToProps(state) {
loading: !!state.user.loading
};
}
export default (connect(mapStateToProps, { fetchCurrentUserRequest, requestLogout })(withStyles(styles)(withWidth()(Header))));
export default withStyles(styles)(connect(mapStateToProps, { fetchCurrentUserRequest, requestLogout })(Header));
+20 -103
View File
@@ -2,35 +2,22 @@ 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',
alignItems: 'center',
justifyContent: 'center',
flexWrap: 'wrap',
justifyContent: 'space-around',
overflow: 'hidden',
width: '100%',
},
rootGridList: {
margin: '0 !important',
backgroundColor: theme.palette.background.paper,
},
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)',
marginBottom: theme.spacing(4),
scrollbarWidth: 'none',
'&::-webkit-scrollbar': {
width: 0,
}
height: '100%',
},
gridListTile: {
display: 'flex',
@@ -38,114 +25,44 @@ const styles = theme => ({
alignItems: 'center',
justifyContent: 'center',
height: '100% !important',
//margin: '0 3vw',
padding: '16px !important',
[theme.breakpoints.up('md')]: {
margin: '0',
paddingTop: '5vh',
'&:first-child': {
marginLeft: '3vw',
},
},
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 {
state = {
defaultIndex: 2,
direction: '',
}
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 => {
const { classes } = this.props
const children =(tile)=> React.Children.map(this.props.children, child => {
return React.cloneElement(child, {
tile
});
});
});
return (
<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)}
<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) }
</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)(withWidth()(HorizontalScroller))
export default withStyles(styles)(HorizontalScroller)
@@ -1,179 +1,34 @@
import React, { Component, Fragment } from 'react';
import { connect } from "react-redux";
import Validator from "validator";
import { subscribeToNewsletterRequest } from "../../actions/user";
import React, { Component } from 'react'
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'
import TextField from '@material-ui/core/TextField';
const styles = theme => ({
root: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
[theme.breakpoints.up('sm')]: {
flexDirection: 'row',
},
textField: {
textAlign: 'center',
justifyContent: 'center',
},
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() {
const { classes } = this.props;
const { data, success, errors, loading, openSnack } = this.state;
let {classes} = this.props
return (
<section className={classes.root}>
<Typography
gutterBottom
component="h3"
className={classes.text}
>
Recevez les dernières nouvelles de <span>Talents Tube</span> par e-mail
<div>
<Typography gutterBottom component="h3">
Recevez les dernières nouvelles de Talents Tube par e-mail
</Typography>
<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>
<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>
)
}
}
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)));
export default withStyles(styles)(NewsLetterRegistration)
-471
View File
@@ -1,471 +0,0 @@
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));
+81 -120
View File
@@ -1,112 +1,83 @@
import React, { Fragment } from 'react';
import React 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',
minHeight: '21vh',
maxHeight: '32vh',
borderRadius: '5px',
position: 'relative',
height: '50vh',
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')]: {
minHeight: '15vh',
maxHeight: '20vh',
width : '30%',
},
[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
},
detailsContainer: {
details_container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start',
padding: theme.spacing(2),
width: '75%',
alignItems: 'center',
padding: '10vh 0 0 0',
},
jobTitle: {
company_title: {
fontSize: '1em',
fontWeight: 'bold',
color: theme.palette.offerlistitem.title,
},
companyTitle: {
fontSize: '0.8em',
fontWeight: '300',
color: theme.palette.offerlistitem.title,
fontWeight: 'normal',
textTransform: 'uppercase',
},
content: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
flexWrap: 'wrap',
width: '100%',
padding: 0,
},
cover: {
position: 'relative',
width: '125px',
height: '100%',
backgroundPosition: 'center',
width: '100%',
height: '50%',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundBlendMode: 'darken',
backgroundColor: 'rgba(30,35,39,0.25)',
transition: 'background-color 1s',
[theme.breakpoints.up('md')]: {
width: '35%',
},
},
iconPlay: {
fontSize: '2em',
color: theme.palette.background.grey,
logo_container: {
display: 'flex',
alignItems: 'center',
width: 100,
height: 100,
position: 'absolute',
top: 'calc(50% - 0.5em)',
left: 'calc(50% - 0.5em)',
left: '50%',
top: '50%',
transform: 'translate(-50%, -66%)',
backgroundColor: 'white',
boxShadow: '0 8px 6px -6px grey',
},
iconText: {
marginLeft: 0,
justifyContent: 'flex-start',
logo: {
width: '100%',
height: 'auto',
objectfit: 'contain',
borderRadius: 0,
},
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,
}
});
@@ -115,61 +86,51 @@ class OffreRowComponent extends React.Component {
render() {
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 { classes, rowData } = this.props;
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>
<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} />
)}
<Card className={classes.card} onClick={() => Router.push(`/Offre?id=${rowData.id}`)}>
<CardContent className={classes.content}>
<CardMedia
className={classes.cover}
image={imgUrl}
title={rowData.title}
/>
{!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.logo_container}>
<Avatar className={classes.logo} src={logoUrl} />
</div>
</div>
</div>
</CardContent>
</Card>
</Link>
<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>
);
}
}
+55 -99
View File
@@ -8,124 +8,85 @@ 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: false
buttonDisplay: true
}
isAvailable = () => {
isAvailable = () =>{
console.log(this.buttonDisplay)
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() {
window.addEventListener('beforeinstallprompt', () => {
this.BeforePromptInstallTrigger()
})
if (!!window.deferredPrompt)
this.setState({ buttonDisplay: true });
if (typeof window !== 'undefined') {
if (window !== undefined) {
// don't show if we are in App
if (window.navigator.standalone ||
window.matchMedia('(display-mode: standalone)').matches) {
this.setState({ openSnack: false, buttonDisplay: false })
window.matchMedia('(display-mode: standalone)').matches)
{
console.log('standalone')
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 })
else if(this.IsSafari())
{
console.log('safari')
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 })
if (typeof window !== 'undefined') {
console.log('prompted')
if (window !== undefined) {
window.promptInstall();
}
}
@@ -144,40 +105,38 @@ 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 component="h2">
Suivez les instructions ci -dessous :
<Typography gutterBottom variant="h5" component="h2">
-> les PWA doivent encore s'installer manuellement sous IOS
</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" className={classes.buttonCompris} color="primary" onClick={this.handleClose}>
j'ai compris
<Button name="compris" size="small" color="secondary" onClick={this.handleClose}>
j'ai compris
</Button>
</CardActions>
</CardActions>
</CardActionArea>
</Card>);
var usualSnack = (<Card className={classes.cardSnack}>
<CardActionArea>
<CardContent>
<Typography gutterBottom component="h2" className={ classes.askToInstallText }>
<Typography gutterBottom variant="h5" component="h2">
Voulez-vous installer notre application ?
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button className={classes.button} color="primary" name='oui' size="small" onClick={this.PromptA2HS}>
<Button name='oui' size="small" color="secondary" onClick={this.PromptA2HS}>
oui
</Button>
<Button className={classes.button} color="primary" name='non' size="small" onClick={this.handleClose}>
<Button name='non' size="small" color="secondary" onClick={this.handleClose}>
non
</Button>
</CardActions>
@@ -185,9 +144,8 @@ class PWAInstallSnack extends Component {
return <Fragment>
return <Fragment>
<Snackbar
className={ classes.Snackbar }
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
open={this.state.openSnack}
onClose={this.handleClose}
@@ -196,11 +154,9 @@ class PWAInstallSnack extends Component {
}}>
{this.IsSafari() ? safariSnack : usualSnack}
</Snackbar>
<Collapse in={this.state.buttonDisplay}>
{this.state.buttonDisplay && this.props.children(this.getStateAndHelpers())}
</Collapse>
</Fragment>
{this.state.buttonDisplay && this.props.children(this.getStateAndHelpers())}
</Fragment>
}
}
+27 -58
View File
@@ -4,6 +4,7 @@ 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';
@@ -15,66 +16,26 @@ 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(1),
margin: theme.spacing.unit,
maxWidth: '400px', [theme.breakpoints.down('md')]: {
maxWidth: `${100 - (theme.spacing(1))}vw`
},
sendContainer: {
textAlign: 'center'
maxWidth: `${100 - (theme.spacing.unit)}vw`
},
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, videoJsOptions, () => {
this.player = videojs(this.videoNode, this.props, () => {
// print version information at startup
var version_info = 'Using video.js ' + videojs.VERSION +
' with videojs-record ' + videojs.getPluginVersion('record') +
@@ -84,14 +45,12 @@ class Recorder extends Component {
// device is ready
this.player.on('deviceReady', () => {
console.info('device is ready!');
this.setState({canSend:false});
console.log('device is ready!');
});
// 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
@@ -99,22 +58,19 @@ 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();
@@ -122,11 +78,24 @@ 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
this.props.uploader(formData);
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')
}
render() {
@@ -136,12 +105,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 className={classes.sendContainer}>
{ this.state.canSend && <Button type="submit" variant="outlined" color="primary"
<div>
<Button type="submit" variant="outlined" color="secondary"
className={classes.button}
onClick={this.sendBlob}>
Envoyer
</Button>}
Send
</Button>
</div>
</div>
);
+79 -167
View File
@@ -1,28 +1,21 @@
import React, { Fragment } from 'react';
import clsx from 'clsx';
import React, {Fragment} from 'react';
import { connect } from "react-redux";
import Validator from "validator";
import { TextField, FormControlLabel, Checkbox, Button, Typography, Link } from '@material-ui/core';
import {FormLabel, FormHelperText,InputLabel, Input,TextField, FormGroup, 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%'
@@ -31,75 +24,13 @@ const styles = theme => ({
display: 'flex',
flexDirection: 'column',
},
formText: {
padding: theme.spacing(0, 2),
paddingTop: theme.spacing(3),
fontSize: '0.9em',
fontWeight: '300',
input50: {
width: '50%',
},
formLink: {
paddingLeft: theme.spacing(1),
fontWeight: '500',
color: theme.palette.primary.dark,
},
checkbox: {
display: 'flex',
alignItems: 'center',
checkboxError:{
color:'red'
}
'& .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 {
@@ -112,15 +43,9 @@ class Register extends React.Component {
checkNewsletter: false,
checkCGU: false,
},
errors: {
email:null,
password:null,
firstname:null,
lastname:null,
checkCGU:null
},
errors: {},
loading: false,
showPassword: false
showPassword:false
}
validate = data => {
@@ -130,17 +55,19 @@ 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)
@@ -150,7 +77,7 @@ class Register extends React.Component {
};
UNSAFE_componentWillReceiveProps(nextProps) {
componentWillReceiveProps(nextProps) {
this.setState({
errors: nextProps.errors,
loading: nextProps.loading
@@ -161,7 +88,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 => {
@@ -175,34 +102,38 @@ 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}>
<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}
/>
<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.email}
@@ -212,93 +143,80 @@ class Register extends React.Component {
label="Email"
margin="normal"
type="email"
variant="outlined"
required
error={!!errors.email}
autoComplete="username email"
/>
<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: (
<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={
<InputAdornment position="end">
<IconButton
edge="end"
aria-label="Toggle password visibility"
onClick={this.handleClickShowPassword}
>
<IconButton 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
id="cypCheckboxNewsletter"
value={data.checkNewsletter}
<Checkbox 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.checkbox + ' ' + classes.checkboxError : classes.checkbox}
className={!!errors.checkCGU&&classes.checkboxError}
control={
<Checkbox
id="cypCheckboxRegister"
<Checkbox
error={!!errors.checkCGU }
value={data.checkCGU}
onChange={this.handleCheckBoxChange('checkCGU')} required
onChange={this.handleCheckBoxChange('checkCGU')}
/>
}
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="contained" className={classes.button + ' ' + classes.buttonImportant + ' cypButtonRegister'} onClick={this.onRegister}>
<Button type="submit" variant="outlined" color="primary" onClick={this.onRegister}>
S'inscrire
</Button>
<Typography
paragraph={true}
align="center"
className={classes.conditionalText}
>Ou</Typography>
</form>
<SocialButtonsAuth linkedInAuth={this.props.linkedInAuth} facebookAuth={this.props.facebookAuth} />
<div classes={classes.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}>
<Typography
paragraph={true}
align="center"
className={classes.formText}
>Je possède un compte Talents Tube ?
<Link href={"/Login"} >
<span className={classes.formLink}>
Se connecter
</span>
<Link href={"/Login"} className={classes.link}>
Se connecter
</Link>
</Typography>
</div>
</div>
);
@@ -308,13 +226,7 @@ class Register extends React.Component {
function mapStateToProps(state) {
return {
loading: !!state.user.loading,
errors: !!state.user.errors ?state.user.errors: {
email:null,
password:null,
firstname:null,
lastname:null,
checkCGU:null
}
errors: state.user.errors
};
}
+54 -133
View File
@@ -1,12 +1,9 @@
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: {
@@ -23,100 +20,21 @@ 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',
},
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',
},
plyr: {
'.plyr--video .plyr__controls': {
background: null
}
}
});
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' }
]
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' }]
class ShortVideoHooker extends Component {
constructor(props) {
@@ -136,7 +54,7 @@ class ShortVideoHooker extends Component {
componentDidMount() {
this.timer = setInterval(this.tick.bind(this), 2000);
this.timer = setInterval(this.tick.bind(this), 500);
}
componentWillUnmount() {
@@ -144,7 +62,7 @@ class ShortVideoHooker extends Component {
}
tick() {
// console.log(this.state)
if (new Date() - this.state.startDate >= 8000) {
if (this.state.i == 2)
@@ -152,13 +70,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;
}
}
}
@@ -166,42 +84,45 @@ class ShortVideoHooker extends Component {
render() {
let { classes } = this.props;
let videoId = videoList[0].vimeoId;
// console.log(videoId)
return (
<div className={classes.video}>
<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>
{/* 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>
<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
<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
</Button>
</Link>
</div>
)
}
}
export default withStyles(styles)(withWidth()(ShortVideoHooker))
export default withStyles(styles)(ShortVideoHooker);
@@ -1,69 +0,0 @@
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>
);
}
+55 -62
View File
@@ -5,80 +5,79 @@ 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 KeyboardArrowleftIcon from '@material-ui/icons/PlayArrowRounded';
import Link from 'next/link';
import _ from 'lodash';
import Typography from '@material-ui/core/Typography';
const styles = theme => ({
card: {
display: 'flex',
height: '400px',
position: 'relative',
height: '60vh',
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)',
},
[theme.breakpoints.up('md')]: {
width: '30%',
},
[theme.breakpoints.down('md')]: {
width: '40%',
},
[theme.breakpoints.down('xs')]: {
width: '100%',
},
margin: '2vh 3vh 3vh 1vh',
[theme.breakpoints.up('sm')]: {
margin: '1vh 0 1vh 0',
height: '420px',
},
[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: '45%',
height: '50%',
backgroundSize: 'cover',
backgroundRepeat: 'no-repeat',
backgroundBlendMode: 'darken',
backgroundColor: 'rgba(30,35,39,0.25)',
transition: 'background-color 1s',
},
logoContainer: {
logo_container: {
display: 'flex',
alignItems: 'center',
width: 80,
height: 80,
width: 70,
height: 70,
position: 'absolute',
left: '50%',
top: '45%',
top: '50%',
transform: 'translate(-50%, -66%)',
border: 'solid 5px white',
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)',
boxShadow: '0 8px 6px -6px grey',
border: 'solid 5px white',
borderRadius: '0.5em',
},
logo: {
width: '100%',
height: 'auto',
objectfit: 'contain',
borderRadius: 'inherit',
},
chip: {
margin: '0 1vh 0 1vh',
@@ -91,34 +90,28 @@ 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 logoName = (rowData.company) ? rowData.company.logo : rowData.logo
const logoUrl = publicRuntimeConfig.symphonyUrl + publicRuntimeConfig.cachedMediaUrl + logoName
const { classes, rowData, children } = this.props;
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
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={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>
<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>
);
}
}
@@ -126,7 +119,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);
+27 -46
View File
@@ -3,88 +3,69 @@ import PropTypes from "prop-types";
import Router from 'next/router'
import getConfig from 'next/config'
import { withStyles } from '@material-ui/core/styles';
import Typography from '@material-ui/core/Typography';
import Chip from '@material-ui/core/Chip';
import IconText from './../atoms/IconText';
import Typography from '@material-ui/core/Typography';
const styles = theme => ({
detailsContainer: {
details_container: {
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'space-between',
position: 'absolute',
top: '60%',
width: '100%',
minHeight: '35%',
padding: '8vh 0 0 0',
},
companyTitle: {
company_title: {
fontSize: '1em',
paddingLeft: theme.spacing(1),
paddingRight: theme.spacing(1),
minHeight: '8vh',
[theme.breakpoints.up('lg')]: {
minHeight: 'inherit',
},
fontWeight: 'normal',
textTransform: 'uppercase',
},
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 CompanyOffre extends React.Component {
class TileCompany 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>
<section className={classes.detailsContainer}>
<div className={classes.details_container}>
<Typography
gutterBottom
variant="headline"
variant="h6"
component="h4"
align="center"
className={classes.company_title}>
{tile.company.business_name}
</Typography>
<Typography
gutterBottom
variant="headline"
variant="h6"
component="h3"
align="center"
className={classes.companyTitle}>
{tile.brand_name ? tile.brand_name : "-"}
className={classes.job_title}>
{tile.title}
</Typography>
{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>
</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>
</Fragment>
);
}
}
CompanyOffre.propTypes = {
TileCompany.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
tile: PropTypes.object.isRequired
};
export default withStyles(styles, { withTheme: true })(CompanyOffre);
export default withStyles(styles, { withTheme: true })(TileCompany);
+27 -46
View File
@@ -1,44 +1,31 @@
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 => ({
detailsContainer: {
details_container: {
display: 'flex',
flexDirection: 'column',
justifyContent: 'space-between',
position: 'absolute',
top: '51%',
width: '100%',
alignItems: 'center',
padding: '8vh 0 0 0',
},
companyTitle: {
company_title: {
fontSize: '0.8em',
fontWeight: '100',
padding: theme.spacing(0, 2),
marginTop: '4vh',
minHeight: '6vh',
[theme.breakpoints.up('md')]: {
minHeight: '3vh',
},
fontWeight: 'normal',
},
jobTitle: {
fontSize: '1em',
padding: theme.spacing(0, 2),
minHeight: '8vh',
[theme.breakpoints.up('lg')]: {
minHeight: '50px',
},
job_title: {
fontSize: '1em',
marginLeft: theme.spacing(1),
marginRight: theme.spacing(1),
},
rootIconText: {
margin: 0,
padding: theme.spacing(0.5, 2),
color: theme.palette.secondary.dark,
justifyContent: 'flex-start',
'& p ': {
color: theme.palette.secondary.text,
}
chip: {
margin: '0 1vh 0 1vh',
}
});
@@ -51,37 +38,31 @@ class TileOffre extends React.Component {
return (
<Fragment>
<section className={classes.detailsContainer} >
<section className={classes.details_container}>
<Typography
gutterBottom
variant="headline"
variant="h6"
component="h4"
align="center"
className={classes.companyTitle}>
{tile.company.brand_name && tile.company.brand_name}
className={classes.company_title}>
{tile.company.business_name}
</Typography>
<Typography
gutterBottom
variant="headline"
variant="h6"
component="h3"
align="left"
className={classes.jobTitle}>
{tile.title && tile.title}
align="center"
className={classes.job_title}>
{tile.title}
</Typography>
{/*
<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>
<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>
</section>
</Fragment>
);
+24
View File
@@ -0,0 +1,24 @@
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)
+48 -225
View File
@@ -1,67 +1,15 @@
import Griddle, { utils } from "griddle-react";
import PropTypes from 'prop-types';
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 React, { Component } from 'react';
import { connect } from "react-redux";
import OffreRow from "../organisms/OffreRow"
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}>
@@ -76,145 +24,65 @@ class Grid extends Component {
success: false,
data: {},
currentPage: 1,
pageSize: 12,
pageSize: 10,
recordCount: 0,
promoted: false,
filterText: "",
filterText: ""
};
async componentDidMount() {
this.setState({pageSize:this.props.limit})
this.props.dataFetcher(
{
offset: this.state.currentPage,
limit: this.props.limit
limit: this.state.pageSize
}
);
}
UNSAFE_componentWillReceiveProps(nextProps) {
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, promoted } = this.state;
this.props.dataFetcher({ offset: currentPage * pageSize, limit: pageSize, keyword: filterText, isPromoted: promoted })
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 })
};
_onPrevious = () => {
const { currentPage, pageSize, filterText, promoted } = this.state;
this.props.dataFetcher({ offset: (currentPage - 2) * pageSize + 1, limit: pageSize, keyword: filterText, isPromoted: promoted });
const { currentPage, pageSize, filterText } = this.state;
this.props.dataFetcher({ offset: (currentPage - 2) * pageSize + 1, limit: pageSize, keyword: filterText });
};
_onGetPage = pageNumber => {
const { pageSize, filterText, promoted } = this.state;
this.props.dataFetcher({ offset: (pageNumber - 1) * pageSize + 1, limit: pageSize, keyword: filterText, isPromoted: promoted });
const { pageSize, filterText } = this.state;
this.props.dataFetcher({ offset: (pageNumber - 1) * pageSize + 1, limit: pageSize, keyword: filterText });
};
_onFilter = filterText => {
this.setState({ currentPage: 1 });
const { currentPage, pageSize, promoted } = this.state;
const { currentPage, pageSize } = this.state;
this.setState({ filterText });
this.props.dataFetcher({ offset: (currentPage - 1) * pageSize + 1, limit: pageSize, keyword: filterText, isPromoted: promoted });
this.props.dataFetcher({ offset: (currentPage - 1) * pageSize + 1, limit: pageSize, keyword: filterText });
};
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'
},
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',
},
TableBody: { display: "flex", flexWrap: "wrap", justifyContent: "space-around" },
}
}
const { filterText, data, currentPage, pageSize, recordCount, loading } = this.state;
const { data, currentPage, pageSize, recordCount } = this.state;
// console.debug("-- render -- ")
// console.debug(data)
const NewLayout = ({ Table, Pagination, Filter }) => (
<div>
{/* <Filter /> */}
<Table />
<Filter />
<Pagination />
<Table />
</div>
);
@@ -235,84 +103,39 @@ class Grid extends Component {
return (
<div>
{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.' />)
}
<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}
/>
</div>
)
}
}
Grid.defaultProps = {
offset: 1,
limit: 12,
searchable: true
}
Grid.propTypes = {
dataFetcher: PropTypes.func.isRequired,
CustomRowComponent: PropTypes.any.isRequired,
data: PropTypes.any,
searchable: PropTypes.bool
data: PropTypes.any
}
export default withStyles(styles)(Grid);
export default Grid;
+28 -10
View File
@@ -1,25 +1,43 @@
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: '64px',
background: theme.palette.secondary.main,
height: '56px',
background: 'silver',
position: 'fixed',
zIndex: '1199',
[theme.breakpoints.up('sm')]: {
height: '64px'
}
},
foot: {
height: '64px',
height: '56px',
width: '100%',
position: 'relative',
top: '64px',
top: '56px',
[theme.breakpoints.up('sm')]: {
height: '64px',
top: '64px'
},
},
content: {
top: '64px',
height: 'calc(100vh - 64px)',
top: '56px',
height: 'calc(100vh - 112px)',
[theme.breakpoints.up('sm')]: {
top: '64px',
height: 'calc(100vh - 128px)',
},
position: 'relative',
overflow: 'auto',
}
});
@@ -28,7 +46,7 @@ function Layout(props) {
const { classes } = props;
return (
<RootContainer>
<div className={classes.root}>
<div id="H" className={classes.head}>
<div className={classes.inner}>
<Header searchBar={true}/>
@@ -38,7 +56,7 @@ function Layout(props) {
{props.children}
</div>
</RootContainer>
</div>
)
}
-93
View File
@@ -1,93 +0,0 @@
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)
@@ -1,120 +0,0 @@
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)
@@ -1,129 +0,0 @@
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))
+18 -20
View File
@@ -1,15 +1,17 @@
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%',
zIndex: '999',
height: '64px',
background: theme.palette.secondary.main,
height: '56px',
background: 'silver',
position: 'fixed',
[theme.breakpoints.up('sm')]: {
@@ -18,17 +20,16 @@ const styles = theme => ({
},
content: {
background: theme.palette.background.grid,
top: '64px',
top: '56px',
height: 'calc(100vh - 56px)',
[theme.breakpoints.up('sm')]: {
top: '64px',
height: 'calc(100vh - 64px)',
},
position: 'relative',
overflow: 'auto',
},
footer: {
marginTop: theme.spacing(10),
},
}
});
function LayoutGrid(props) {
@@ -36,28 +37,25 @@ function LayoutGrid(props) {
const { classes } = props;
return (
<RootContainer>
<div className={classes.root}>
<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 id="F" className={classes.footer}>
<Footer />
</div>
</RootContainer>
</div>
)
}
LayoutGrid.defaultProps = {
backLink: "/"
backLink:"/"
}
LayoutGrid.propTypes = {
backLink: PropTypes.string
backLink : PropTypes.string
}
export default withStyles(styles)(LayoutGrid)
+44 -78
View File
@@ -1,115 +1,81 @@
import { withStyles } from '@material-ui/core/styles'
import PropTypes from 'prop-types';
import Header from '../organisms/Header'
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 Header from '../organisms/Header'
import withWidth, { isWidthUp } from '@material-ui/core/withWidth'
import Hidden from '@material-ui/core/Hidden';
import ArrowLeft from '@material-ui/icons/keyboardArrowLeft';
const styles = theme => ({
root: {
height: '100%'
},
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)',
[theme.breakpoints.up('sm')]: {
height: '64px'
},
},
contentDefault: {
height: 'auto',
position: 'relative',
paddingBottom: '10vh',
},
contentProvided: {
height: '100%',
content: {
height: 'calc(100vh - 56px)',
[theme.breakpoints.up('sm')]: {
height: 'calc(100vh - 64px)',
},
position: 'relative',
overflow: 'auto',
},
back:
{
alignSelf: 'flex-start',
position: 'absolute',
zIndex: '999',
color: 'white',
backgroundColor: theme.palette.menu.backArrow,
zIndex:10,
color : 'white',
backgroundColor: 'grey',
border: 'none',
color: 'white',
padding: theme.spacing(0.2),
padding: '10px',
textAlign: 'center',
textDecoration: 'none',
display: 'inlineBlock',
margin: theme.spacing(2),
fontSize: '38px',
margin:' 10px 16px',
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 LayoutOffre(props) {
function Layout(props) {
const { classes, offreId, isProvided } = props;
const contentClass = (!isProvided) ? classes.contentDefault : classes.contentProvided
const { classes } = props;
return (
<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}>
<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}>
{props.children}
</div>
<Hidden mdUp>
{!isProvided && (
<div id="F" className={classes.foot}>
{/* <div className={classes.inner}>
<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} />
<Button variant="outlined" color="secondary" >Postuler</Button>
{/* <div className={classes.grow} />
</div> */}
</div>
)}
</Hidden>
</div>
</RootContainer>
</div>
)
}
LayoutOffre.propTypes = {
classes: PropTypes.object.isRequired,
offre: PropTypes.object,
};
export default withStyles(styles)(withWidth()(LayoutOffre))
export default withStyles(styles)(Layout)
+97 -24
View File
@@ -1,28 +1,101 @@
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,
},
})
import React from 'react'
class Loader extends React.Component {
export default class extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.loader + ' ' + 'alert alert-info'}><CircularProgress color="primary" />Chargement...</div>
)
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%);
}
.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
@@ -1,10 +0,0 @@
{
"baseUrl": "http://localhost:8000/",
"env":{
"nom":"toto",
"prenom": "toto",
"email":"thomasberrodedeclic42@gmail.com",
"password": "123456"
},
"chromeWebSecurity": false
}
Binary file not shown.
@@ -1,28 +0,0 @@
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'))
})
})
@@ -1,54 +0,0 @@
// 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é')
});
});
});
@@ -1,38 +0,0 @@
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')
})
})
@@ -1,13 +0,0 @@
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')
})
})
@@ -1,31 +0,0 @@
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
})
@@ -1,14 +0,0 @@
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') )
})
})
@@ -1,17 +0,0 @@
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') )
})
})
@@ -1,17 +0,0 @@
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
@@ -1 +0,0 @@
{"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
@@ -1 +0,0 @@
{"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
@@ -1,52 +0,0 @@
// ***********************************************************
// 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.

Before

Width:  |  Height:  |  Size: 140 KiB

-46
View File
@@ -1,46 +0,0 @@
// ***********************************************
// 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
@@ -1,31 +0,0 @@
// ***********************************************************
// 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.
+24 -115
View File
@@ -1,138 +1,47 @@
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/' :
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;
});
//axios.defaults.headers.common = { 'X-Requested-With': 'XMLHttpRequest' }
axios.defaults.baseURL = (process.env.NODE_ENV !== 'production') ? 'http://localhost/app_dev.php/' : ''
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: () => {
return axios.get("/api/user").then(res => { return 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})
},
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)
register: (data) => {
console.log(data)
return axios.post("/api/register", { data}).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, 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 } })
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 } })
.then(res => res.data)
}
},
},
companies: {
fetchCompany: id =>
axios.get(`api/companies/${id}`).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}`)
},
fetchCompanies: (limit = "", order = "", keyword = "", offset = 1) =>
axios.get('api/companies', { params: { offset, limit, order, keyword } }).then(res => res.data)
}
};
+1 -1
View File
@@ -215,7 +215,7 @@ module.exports = () => {
}
})
if (process.env.NODE_ENV === 'development') {
console.info('Generated sign in link ' + url + ' for ' + email)
console.log('Generated sign in link ' + url + ' for ' + email)
}
},
// Credentials Sign In
+9 -6
View File
@@ -26,6 +26,7 @@ require('dotenv').load()
module.exports = () => {
let providers = []
if (process.env.FACEBOOK_ID && process.env.FACEBOOK_SECRET) {
providers.push({
providerName: 'Facebook',
providerOptions: {
@@ -33,8 +34,8 @@ module.exports = () => {
},
Strategy: require('passport-facebook').Strategy,
strategyOptions: {
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,
clientID: process.env.FACEBOOK_ID,
clientSecret: process.env.FACEBOOK_SECRET,
profileFields: ['id', 'displayName', 'email', 'link']
},
getProfile(profile) {
@@ -46,8 +47,9 @@ module.exports = () => {
}
}
})
}
if (process.env.LINKEDIN_ID && process.env.LINKEDIN_SECRET) {
providers.push({
providerName: 'Linkedin',
providerOptions: {
@@ -55,11 +57,12 @@ module.exports = () => {
},
Strategy: require('@sokratis/passport-linkedin-oauth2').Strategy,
strategyOptions: {
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,
clientID: process.env.LINKEDIN_ID,
clientSecret: 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,
@@ -68,7 +71,7 @@ module.exports = () => {
}
}
})
}
return providers
}
+45 -42
View File
@@ -4,53 +4,47 @@ 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: {
module.exports = withPlugins([
[withCSS],
[optimizedImages, {
/* config for next-optimized-images */
}],
[withOffline, {
dontAutoRegisterSw: DEV ? true : false,
workboxOpts: {
globPatterns: ['static/**/*'],
globDirectory: '.',
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/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-"
},
}
}]
],
['/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/'
}
}]],
{
webpack: (config, { isServer }) => {
@@ -74,11 +68,20 @@ module.exports = withPlugins(
RecordRTC: "recordrtc"
};
config.resolve.alias = { ...config.resolve.alias, ...videojsAlias };
config.plugins.push(videojsPlugin);
config.plugins.push(new Dotenv());
// config.module.rules.push(
// {
// test: /\.css$/,
// loader: 'style-loader!css-loader',
// include: [/node_modules/]
// },
// )
console.log(config);
return config
}
},
+8
View File
@@ -0,0 +1,8 @@
# crawl setup
User-agent: *
Disallow: /Login*
# Sitemap
Sitemap: https://app.talentstube.com/sitemap_local.xml
-28
View File
@@ -1,28 +0,0 @@
<?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-10-18</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-10-18</lastmod>
</url>,<url>
<loc>https://app.talentstube.com/Offres</loc>
<lastmod>2019-10-16</lastmod>
</url>
</urlset>
+12 -33
View File
@@ -1,5 +1,5 @@
{
"name": "talentstube",
"name": "nextjs",
"version": "1.0.0",
"private": true,
"resolutions": {
@@ -7,26 +7,22 @@
},
"dependencies": {
"@blunck/next-alias": "^1.0.0",
"@material-ui/core": "^4.2.0",
"@material-ui/core": "^4.0.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": "^9.1.1",
"next": "8.1.0",
"next-auth": "^1.11.0",
"next-compose-plugins": "^2.1.1",
"next-offline": "^4.0.2",
@@ -40,51 +36,34 @@
"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": "^16.9.0",
"react-dom": "^16.9.0",
"react": "latest",
"react-dom": "latest",
"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": "^1.0.0",
"style-loader": "^0.23.1",
"terser": "3.14",
"universal-cookie": "^3.0.4",
"validator": "^11.0.0",
"videojs-record": "^3.8.0",
"video.js": "latest",
"videojs-record": "latest",
"webrtc-adapter": "^7.2.3"
},
"scripts": {
"dev": "cross-env PORT=3000 node server.js",
"build": "next build && yarn postExport && yarn copysw && yarn copySiteMap",
"start": "cross-env PORT=8000 NODE_ENV=production 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/"
"build": "next build",
"start": "cross-env PORT=8000 NODE_ENV=production node server.js"
},
"devDependencies": {
"gmail-tester": "^1.1.3",
"webpack-bundle-analyzer": "^3.3.2"
},
"eslint.packageManager": "yarn"
+21 -23
View File
@@ -1,69 +1,67 @@
import React, { Component, Fragment } from 'react'
import React, { Component } 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 Loader from './../components/templates/loader'
import CircularProgress from '@material-ui/core/CircularProgress';
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>
<Loader />
<div className="alert alert-info"><CircularProgress color="secondary" />Chargement...</div>
</div>
</Fragment>
)
}
}
AuthRouter.getInitialProps = function (context) {
console.log("--AuthRouter--")
const { JWT } = context.query
return { JWT: JWT };
console.log(context.query)
//context.store.dispatch(fetchAdRequest(id))
//console.log(url)
console.log(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
@@ -1,228 +0,0 @@
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))
+17 -32
View File
@@ -2,15 +2,17 @@
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 AnswerRow from "../components/organisms/AnswerRow"
import { fetchAdsAnsweredRequest } from '../actions/answer'
import { AnswersSelector } from '../reducers/answers'
import Head from 'next/head'
import OffreRow from "../components/organisms/OffreRow"
import { fetchAdsRequest } from '../actions/ads'
import { AdsSelector } from '../reducers/ads'
const styles = theme => ({
root: {
@@ -22,16 +24,7 @@ const styles = theme => ({
container: {
display: 'flex',
flexDirection: 'column',
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),
height: '100%',
},
});
@@ -43,24 +36,17 @@ class Candidatures extends React.Component {
return (
<LayoutGrid backLink="/">
<Head>
<title key="title">Candidatures</title>
</Head>
{/* TODO */}
<Typography component="h1" align="center" className={classes.title} gutterBottom>
Mes candidatures
<Typography variant="h2" component="h3" align="center" gutterBottom>
Mes candidatures //TODO
</Typography>
<div className={classes.container}>
<Grid CustomRowComponent={AnswerRow}
dataFetcher={this.props.fetchAdsAnsweredRequest}
data={this.props.answers}
<Grid CustomRowComponent={OffreRow}
dataFetcher={this.props.fetchAdsRequest}
data={this.props.ads}
currentPage={this.props.currentPage}
recordCount={this.props.recordCount}
loading={this.props.loading}
placeholder={"Rechercher une réponse"}
searchable={false}
/>
</div>
</LayoutGrid>
@@ -75,12 +61,11 @@ Candidatures.propTypes = {
function mapStateToProps(state) {
return {
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)
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)
};
}
export default withStyles(styles)(connect(mapStateToProps, {fetchAdsAnsweredRequest })(Candidatures))
export default withStyles(styles)(connect(mapStateToProps, { fetchAdsRequest })(Candidatures))
-499
View File
@@ -1,499 +0,0 @@
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
@@ -0,0 +1,66 @@
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
@@ -0,0 +1,77 @@
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
@@ -1,362 +0,0 @@
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
@@ -1,126 +0,0 @@
import React, { Component, Fragment } from 'react';
import { withStyles } from '@material-ui/core/styles';
import { connect } from 'react-redux';
import { requestConfirmationAccountRequest } from '../actions/user';
import Loader from './../components/templates/loader'
import Typography from '@material-ui/core/Typography'
import Button from '@material-ui/core/Button';
import LayoutBack from '../components/templates/LayoutBack';
import Router from 'next/router';
import Head from 'next/head'
const styles = theme => ({
container: {
display: 'flex',
flexDirection: 'column',
position: 'relative',
height: '100%',
},
bottomContainer: {
display: 'flex',
flexDirection: 'column',
alignSelf: 'center',
justifyContent: 'center',
position: 'absolute',
bottom: 0,
},
title: {
fontFamily: 'Dosis',
fontSize: '1.5em',
fontWeight: '600',
padding: theme.spacing(3, 0),
},
text: {
fontSize: '1em',
fontWeight: '300',
padding: theme.spacing(1, 0),
},
bottomText: {
fontWeight: '500',
},
button: {
textTransform: 'inherit',
marginTop: theme.spacing(4),
marginBottom: theme.spacing(2),
padding: theme.spacing(1.2, 1),
borderRadius: '3px',
color: theme.palette.secondary.main,
backgroundColor: theme.palette.primary.important,
},
})
class ConfirmAccount extends Component {
state = {
errors: {},
loading: false,
};
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
errors: nextProps.errors,
loading: nextProps.loading,
})
}
render() {
const { classes } = this.props
const { loading } = this.state
return (
<LayoutBack text="Validation" height="fixed">
<Head>
<title key="title">Confirmation de compte</title>
<meta key="robots" name="robots" content="noindex, nofollow"></meta>
</Head>
<div className={classes.container}>
{loading && (
<Loader />
)}
{!loading && (
<Fragment>
<Typography gutterBottom align="center" className={classes.title}>
e-mail validé
</Typography>
<Typography gutterBottom align="left" className={classes.text}>
Votre email a bien été validé.
</Typography>
<Typography gutterBottom align="left" className={classes.text}>
Vous pouvez maintenant vous connecter.
</Typography>
<section className={classes.bottomContainer}>
<Button
variant="contained"
className={classes.button}
classes={{ root: classes.rootButton }}
onClick={() => Router.push('/Login')}
>
Connexion
</Button>
</section>
</Fragment>
)}
</div>
</LayoutBack>
)
}
}
function mapStateToProps(state, ownProps) {
return {
loading: !!state.user.loading,
errors: state.user.errors
};
}
ConfirmAccount.getInitialProps = function (context) {
const { token, email } = context.query
context.store.dispatch(requestConfirmationAccountRequest({ 'confirmationToken': token, 'email': email }))
return email;
}
export default withStyles(styles)(connect(mapStateToProps, { requestConfirmationAccountRequest })(ConfirmAccount));

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