Compare commits
6 Commits
V2.6.2_prod
...
auth
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dbfad4d7d | |||
| db10c3d820 | |||
| 5f9b40e9f7 | |||
| 516cdf176b | |||
| 3235543cd7 | |||
| f7f8b98804 |
@@ -1,15 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
SERVER_URL=http://localhost:80
|
||||
MONGO_URI=mongodb://declic:declic42@ds115653.mlab.com:15653/next-auth
|
||||
FACEBOOK_ID=2332071840142634
|
||||
FACEBOOK_SECRET=afb8bbca72bd8c517c64f81e998e4f12
|
||||
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
|
||||
@@ -0,0 +1,6 @@
|
||||
/node_modules/
|
||||
/.next/
|
||||
/out/
|
||||
TODO
|
||||
/cypress/*
|
||||
!/cypress/integration
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "attach",
|
||||
"name": "Attach to Chrome",
|
||||
"port": 3000,
|
||||
"webRoot": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"name": "Launch Chrome against localhost",
|
||||
"url": "http://localhost:3000",
|
||||
"webRoot": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
# pwa-boilerplate
|
||||
|
||||
Startup React.js project with docker
|
||||
|
||||
|
||||
with next.js 7
|
||||
@@ -0,0 +1,82 @@
|
||||
import React, { Component } from 'react'
|
||||
import { Snackbar } from '@material-ui/core';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardActionArea from '@material-ui/core/CardActionArea';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import IconButton from '@material-ui/core/IconButton';
|
||||
import CloseIcon from '@material-ui/icons/Close';
|
||||
import cookie from 'react-cookies'
|
||||
|
||||
const styles = theme => ({
|
||||
cardSnack: {
|
||||
maxWidth: '100%'
|
||||
}
|
||||
});
|
||||
class CookiesRGPD extends Component {
|
||||
state = {
|
||||
openSnack: false
|
||||
}
|
||||
handleClose = () => {
|
||||
this.setState({ openSnack: false });
|
||||
cookie.save('RGPD', true, {
|
||||
path: '/'
|
||||
});
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
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 })
|
||||
} else {
|
||||
|
||||
this.setState({ openSnack: !cookie.load('RGPD') })
|
||||
}
|
||||
}
|
||||
}
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<Snackbar
|
||||
anchorOrigin={{
|
||||
vertical: 'bottom',
|
||||
horizontal: 'left',
|
||||
}}
|
||||
autoHideDuration={null}
|
||||
open={this.state.openSnack}
|
||||
onClose={this.handleClose}
|
||||
>
|
||||
<Card className={classes.cardSnack}>
|
||||
<CardActionArea>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="h2">
|
||||
Utilisation des cookies
|
||||
</Typography>
|
||||
<Typography component="p">
|
||||
En poursuivant votre navigation, vous acceptez l'utilisation de cookies ou technologies similaires, y compris de partenaires tiers pour la diffusion de publicité ciblée et de contenus pertinents au regard de vos centres d'intérêts. En savoir plus.
|
||||
Afin de continuer à améliorer la protection de vos données personnelles, nous avons mis à jour notre politique de confidentialité.
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<IconButton
|
||||
key="close"
|
||||
aria-label="Close"
|
||||
color="inherit"
|
||||
className={classes.close}
|
||||
onClick={this.handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Snackbar>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(CookiesRGPD)
|
||||
@@ -0,0 +1,245 @@
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Hidden from '@material-ui/core/Hidden';
|
||||
import CardMedia from '@material-ui/core/CardMedia';
|
||||
import LocalPhone from '@material-ui/icons/LocalPhone';
|
||||
import LocationOn from '@material-ui/icons/LocationOn';
|
||||
import Security from '@material-ui/icons/Security';
|
||||
import Mail from '@material-ui/icons/Mail';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Link from 'next/link'
|
||||
import { truncate } from 'lodash'
|
||||
import Social from './social.js'
|
||||
import WhatshotIcon from '@material-ui/icons/Whatshot';
|
||||
import dynamic from 'next/dynamic'
|
||||
import ButtonBase from "@material-ui/core/ButtonBase";
|
||||
import Router from 'next/router'
|
||||
import { Fragment } from 'react';
|
||||
|
||||
const PWAInstallSnack = dynamic(() => import('./PWAInstallSnack.js'), {
|
||||
ssr: false
|
||||
});
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
flexGrow: 1,
|
||||
backgroundColor: theme.palette.secondary.main,
|
||||
color: theme.palette.secondary.main,
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
width: 'calc( 100% - theme.spacing.unit*2)'
|
||||
},
|
||||
grid: {
|
||||
display: 'flex',
|
||||
width: '99%',
|
||||
margin: 0
|
||||
},
|
||||
|
||||
paper: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: theme.spacing.unit * 2,
|
||||
textAlign: 'center'
|
||||
},
|
||||
pointer: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
item: {
|
||||
|
||||
margin: theme.spacing.unit * 2,
|
||||
minWidth: '200px',
|
||||
maxHeight: '300px',
|
||||
|
||||
},
|
||||
icon: {
|
||||
fontSize: '64px',
|
||||
margin: 'auto',
|
||||
textAlign: 'center'
|
||||
|
||||
},
|
||||
card: {
|
||||
display: 'flex',
|
||||
},
|
||||
cardDetails: {
|
||||
flex: 1,
|
||||
|
||||
},
|
||||
cardMedia: {
|
||||
objectFit: 'cover',
|
||||
width: 150,
|
||||
height: 150
|
||||
},
|
||||
link: {
|
||||
cursor: 'pointer'
|
||||
},
|
||||
|
||||
image: {
|
||||
position: 'relative',
|
||||
height: 200,
|
||||
[theme.breakpoints.down('xs')]: {
|
||||
width: '100% !important', // Overrides inline-style
|
||||
height: 100,
|
||||
},
|
||||
'&:hover, &$focusVisible': {
|
||||
zIndex: 1,
|
||||
'& $imageBackdrop': {
|
||||
opacity: 0.15,
|
||||
},
|
||||
'& $imageMarked': {
|
||||
opacity: 0,
|
||||
},
|
||||
'& $imageTitle': {
|
||||
border: '4px solid currentColor',
|
||||
},
|
||||
},
|
||||
},
|
||||
focusVisible: {},
|
||||
imageButton: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
color: theme.palette.common.white,
|
||||
},
|
||||
imageSrc: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundSize: 'cover',
|
||||
backgroundPosition: 'center 40%',
|
||||
},
|
||||
imageBackdrop: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: theme.palette.common.black,
|
||||
opacity: 0.4,
|
||||
transition: theme.transitions.create('opacity'),
|
||||
},
|
||||
imageTitle: {
|
||||
position: 'relative',
|
||||
padding: `${theme.spacing.unit * 2}px ${theme.spacing.unit * 4}px ${theme.spacing.unit + 6}px`,
|
||||
},
|
||||
imageMarked: {
|
||||
height: 3,
|
||||
width: 18,
|
||||
backgroundColor: theme.palette.common.white,
|
||||
position: 'absolute',
|
||||
bottom: -2,
|
||||
left: 'calc(50% - 9px)',
|
||||
transition: theme.transitions.create('opacity'),
|
||||
},
|
||||
social: {
|
||||
margin: 'auto'
|
||||
}
|
||||
});
|
||||
|
||||
class Footer extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, show } = this.props;
|
||||
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
|
||||
<Grid container className={classes.grid} spacing={16}>
|
||||
<Grid item xs={12} md={12}>
|
||||
<Grid container className={classes.demo} justify="center" spacing={16}>
|
||||
<Grid item className={classes.item}>
|
||||
<PWAInstallSnack >
|
||||
{({ initInstall }) =>
|
||||
|
||||
<Button
|
||||
aria-label='install' variant="contained" color="primary" onClick={initInstall}>
|
||||
<WhatshotIcon ></WhatshotIcon>
|
||||
<Typography variant="h6" color="inherit">
|
||||
Instaler l'app
|
||||
</Typography>
|
||||
</Button>
|
||||
|
||||
}
|
||||
</PWAInstallSnack>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<Grid container className={classes.demo} justify="center" spacing={16}>
|
||||
<Grid item className={classes.item}>
|
||||
<Paper className={classes.paper} >
|
||||
<a href="https://goo.gl/maps/mE4X4Xm4i5L2" target="_blank" rel="noopener">
|
||||
<LocationOn className={classes.icon} />
|
||||
</a>
|
||||
<Typography variant="h5" gutterBottom>e-declic Auray</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item className={classes.item}>
|
||||
|
||||
<ButtonBase
|
||||
name='contact'
|
||||
focusRipple
|
||||
key={'image.title'}
|
||||
className={classes.image}
|
||||
focusVisibleClassName={classes.focusVisible}
|
||||
style={{
|
||||
width: 200, height: 140
|
||||
}}
|
||||
onClick={() => Router.push('/contact')}
|
||||
>
|
||||
<span
|
||||
className={classes.imageSrc}
|
||||
|
||||
/>
|
||||
<span className={classes.imageBackdrop} />
|
||||
<span className={classes.imageButton}>
|
||||
|
||||
<Typography
|
||||
component="span"
|
||||
variant="h5"
|
||||
color="inherit"
|
||||
className={classes.imageTitle}
|
||||
>
|
||||
<Mail className={classes.icon + ' ' + classes.link} />
|
||||
<span className={classes.imageMarked} />
|
||||
</Typography>
|
||||
</span>
|
||||
</ButtonBase>
|
||||
</Grid>
|
||||
|
||||
|
||||
<Grid item className={classes.item}>
|
||||
<Paper className={classes.paper} >
|
||||
<LocalPhone className={classes.icon} />
|
||||
<Link href={"tel:+33297290060"} ><a>
|
||||
<Typography variant="h5" gutterBottom>+33.(0)297 290 060</Typography>
|
||||
</a></Link>
|
||||
</Paper>
|
||||
</Grid>
|
||||
<Grid item className={classes.item}>
|
||||
<Paper className={classes.paper} >
|
||||
<Social className={classes.icon} delay='1000' svgWidth='64'></Social>
|
||||
<Typography variant="h5" gutterBottom>Nous Suivre</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Footer)
|
||||
@@ -0,0 +1,180 @@
|
||||
import React 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 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 List from '@material-ui/core/List';
|
||||
import ListItem from '@material-ui/core/ListItem';
|
||||
import { ListItemText } from '@material-ui/core';
|
||||
|
||||
import { Spring, animated } from 'react-spring'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
width: '100%',
|
||||
'& a':{
|
||||
textDecoration:'none'
|
||||
}
|
||||
},
|
||||
|
||||
grow: {
|
||||
flexGrow: 1,
|
||||
},
|
||||
bar: {
|
||||
},
|
||||
menuButton: {
|
||||
marginLeft: -12,
|
||||
marginRight: 20,
|
||||
[theme.breakpoints.up('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
[theme.breakpoints.down('md')]: {
|
||||
display: 'block',
|
||||
},
|
||||
},
|
||||
menuLogo: {
|
||||
marginLeft: 20,
|
||||
marginRight: 40,
|
||||
cursor: 'pointer'
|
||||
},
|
||||
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')]: {
|
||||
display: 'flex',
|
||||
},
|
||||
},
|
||||
sectionMobile: {
|
||||
display: 'flex',
|
||||
[theme.breakpoints.up('md')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
fullList: {
|
||||
width: 'auto',
|
||||
cursor: 'pointer'
|
||||
},
|
||||
});
|
||||
|
||||
class Header extends React.Component {
|
||||
state = {
|
||||
top: false,
|
||||
left: false,
|
||||
bottom: false,
|
||||
right: false,
|
||||
toggle: true
|
||||
};
|
||||
|
||||
toggleDrawer = (side, open) => () => {
|
||||
this.setState({
|
||||
[side]: open,
|
||||
});
|
||||
};
|
||||
|
||||
toggle = () => this.setState(state => ({ toggle: !state.toggle }))
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<div className={classes.root}>
|
||||
<Drawer anchor="top" open={this.state.top} onClose={this.toggleDrawer('top', false)}>
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={this.toggleDrawer('top', false)}
|
||||
onKeyDown={this.toggleDrawer('top', false)}
|
||||
>
|
||||
<div className={classes.fullList}>
|
||||
<List>
|
||||
<Link href="/" prefetch >
|
||||
<a><ListItem>
|
||||
<ListItemIcon>
|
||||
<HomeIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary='Accueil'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
<Divider />
|
||||
<Link href="/contact" prefetch >
|
||||
<a><ListItem>
|
||||
<ListItemIcon>
|
||||
<SendIcon />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary='contact'>
|
||||
</ListItemText>
|
||||
</ListItem></a>
|
||||
</Link>
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
</Drawer>
|
||||
<AppBar position="static" className={classes.appBar}>
|
||||
<Toolbar>
|
||||
<IconButton className={classes.menuButton} onClick={this.toggleDrawer('top', true)}
|
||||
color="inherit" aria-label="Open drawer">
|
||||
<MenuIcon />
|
||||
</IconButton>
|
||||
|
||||
<Link href="/" prefetch>
|
||||
<a><div onClick={this.toggle}>
|
||||
<Spring native from={{ x: 0 }} to={{ x: this.state.toggle ? 1 : 0 }} config={{ duration: 1000 }}>
|
||||
{({ x }) => (
|
||||
<animated.div
|
||||
style={{
|
||||
opacity: x.interpolate({ output: [1, 1] }),
|
||||
transform: x
|
||||
.interpolate({
|
||||
range: [0, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 1],
|
||||
output: [1, 0.97, 0.9, 1.1, 0.9, 1.1, 1.03, 1]
|
||||
})
|
||||
.interpolate(x => `scale(${x})`)
|
||||
}}>
|
||||
<img alt="e-declic" src='https://www.e-declic.com/files/e-declic/img/logo.png' className={classes.menuLogo} />
|
||||
</animated.div>
|
||||
)}
|
||||
</Spring>
|
||||
</div></a>
|
||||
</Link>
|
||||
|
||||
<Link href="/contact" prefetch >
|
||||
<a><Typography className={classes.title} variant="h5" color="inherit" noWrap>
|
||||
Contact
|
||||
</Typography></a>
|
||||
</Link>
|
||||
</Toolbar>
|
||||
</AppBar>
|
||||
</div >
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
|
||||
export default withStyles(styles)(Header)
|
||||
@@ -0,0 +1,115 @@
|
||||
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.log('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.log('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.log('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={[
|
||||
<Link href={this.state.link}><a>Voir</a></Link>,
|
||||
<IconButton
|
||||
key="close"
|
||||
aria-label="Close"
|
||||
color="inherit"
|
||||
className={classes.close}
|
||||
onClick={this.handleClose}
|
||||
>
|
||||
<CloseIcon />
|
||||
</IconButton>,
|
||||
]}
|
||||
/>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
export default withStyles(styles)(Messager);
|
||||
@@ -0,0 +1,23 @@
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Header from './Header'
|
||||
import Messager from './Messager'
|
||||
|
||||
const layoutStyle = {
|
||||
root:{
|
||||
'& a':{
|
||||
textDecoration:'none!important',
|
||||
color:'#111'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Layout = (props) => (
|
||||
<div className={props.classes.root} >
|
||||
<Messager/>
|
||||
<Header menu={props.menu} />
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default withStyles(layoutStyle)(Layout);
|
||||
@@ -0,0 +1,164 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardActionArea from '@material-ui/core/CardActionArea';
|
||||
import CardActions from '@material-ui/core/CardActions';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
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';
|
||||
|
||||
const styles = theme => ({
|
||||
cardSnack: {
|
||||
maxWidth: 345,
|
||||
},
|
||||
media: {
|
||||
height: 140,
|
||||
}
|
||||
});
|
||||
|
||||
class PWAInstallSnack extends Component {
|
||||
state = {
|
||||
openSnack: false,
|
||||
buttonDisplay: true
|
||||
}
|
||||
|
||||
isAvailable = () =>{
|
||||
console.log(this.buttonDisplay)
|
||||
return this.state.buttonDisplay;
|
||||
}
|
||||
|
||||
initInstall = () =>
|
||||
{
|
||||
this.setState({openSnack: true})
|
||||
}
|
||||
getStateAndHelpers() {
|
||||
return {
|
||||
on: this.state.openSnack,
|
||||
initInstall: this.initInstall,
|
||||
}
|
||||
}
|
||||
iOS() {
|
||||
|
||||
var iDevices = [
|
||||
'iPad Simulator',
|
||||
'iPhone Simulator',
|
||||
'iPod Simulator',
|
||||
'iPad',
|
||||
'iPhone',
|
||||
'iPod'
|
||||
];
|
||||
|
||||
if (!!navigator.platform) {
|
||||
while (iDevices.length) {
|
||||
if (navigator.platform === iDevices.pop()){ return true; }
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (window !== undefined) {
|
||||
// don't show if we are in App
|
||||
if (window.navigator.standalone ||
|
||||
window.matchMedia('(display-mode: standalone)').matches)
|
||||
{
|
||||
console.log('standalone')
|
||||
this.setState({ openSnack: false, buttonDisplay: false })
|
||||
}
|
||||
else if(this.IsSafari())
|
||||
{
|
||||
console.log('safari')
|
||||
this.setState({ openSnack: false, buttonDisplay: false })
|
||||
|
||||
if(this.iOS())
|
||||
this.setState({ openSnack: false, buttonDisplay: true })
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//A2HS 'Add 2 Home Screen'
|
||||
PromptA2HS = (event, reason) => {
|
||||
|
||||
this.setState({ openSnack: false })
|
||||
console.log('prompted')
|
||||
if (window !== undefined) {
|
||||
window.promptInstall();
|
||||
}
|
||||
}
|
||||
IsSafari = () => {
|
||||
if (typeof window !== 'undefined') {
|
||||
var isSafari = /Safari/.test(window.navigator.userAgent) && /Apple Computer/.test(window.navigator.vendor);
|
||||
return isSafari;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
handleClose = () => {
|
||||
this.setState({ openSnack: false });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
var safariSnack = (<Card className={classes.cardSnack}>
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
className={classes.media}
|
||||
image="/static/images/help/addToHomeFull.png"
|
||||
title="homeScreen"
|
||||
/>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="h2">
|
||||
-> les PWA doivent encore s'installer manuellement sous IOS
|
||||
</Typography>
|
||||
</CardContent>
|
||||
<CardActions>
|
||||
<Button name="compris" size="small" color="secondary" onClick={this.handleClose}>
|
||||
j'ai compris
|
||||
</Button>
|
||||
</CardActions>
|
||||
</CardActionArea>
|
||||
|
||||
</Card>);
|
||||
|
||||
var usualSnack = (<Card className={classes.cardSnack}>
|
||||
<CardActionArea>
|
||||
<CardContent>
|
||||
<Typography gutterBottom variant="h5" component="h2">
|
||||
Voulez-vous installer notre application ?
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</CardActionArea>
|
||||
<CardActions>
|
||||
<Button name='oui' size="small" color="secondary" onClick={this.PromptA2HS}>
|
||||
oui
|
||||
</Button>
|
||||
<Button name='non' size="small" color="secondary" onClick={this.handleClose}>
|
||||
non
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>);
|
||||
|
||||
|
||||
|
||||
return <Fragment>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'top', horizontal: 'right' }}
|
||||
open={this.state.openSnack}
|
||||
onClose={this.handleClose}
|
||||
ContentProps={{
|
||||
'aria-describedby': 'message-id',
|
||||
}}>
|
||||
{this.IsSafari() ? safariSnack : usualSnack}
|
||||
</Snackbar>
|
||||
{this.state.buttonDisplay && this.props.children(this.getStateAndHelpers())}
|
||||
</Fragment>
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default withStyles(styles)(PWAInstallSnack)
|
||||
@@ -0,0 +1,80 @@
|
||||
/* global window */
|
||||
import React, { PureComponent } from 'react';
|
||||
import { Transition, animated } from 'react-spring';
|
||||
|
||||
export default class extends PureComponent {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.children = React.Children.toArray(props.children);
|
||||
this.state = {
|
||||
count: React.Children.count(props.children),
|
||||
currentIndex: 0
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.startAnimation();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
clearInterval(this.animation);
|
||||
}
|
||||
|
||||
startAnimation() {
|
||||
this.animation = setInterval(() => {
|
||||
if (window.document.visibilityState === 'hidden') {
|
||||
// tab invisible; pause for one round to avoid flickering
|
||||
this.pauseAnimation = true;
|
||||
return;
|
||||
}
|
||||
if (!this.pauseAnimation) {
|
||||
this.setState({
|
||||
currentIndex: (this.state.currentIndex + 1) % this.state.count
|
||||
});
|
||||
} else {
|
||||
this.pauseAnimation = false;
|
||||
}
|
||||
}, this.props.duration || 1500);
|
||||
}
|
||||
|
||||
render() {
|
||||
const currentIndex = this.state.currentIndex;
|
||||
return (
|
||||
<div>
|
||||
<Transition
|
||||
native
|
||||
keys={currentIndex}
|
||||
initial={null}
|
||||
from={{ opacity: 0, y: -50 }}
|
||||
enter={{ opacity: 1, y: 0 }}
|
||||
leave={{ opacity: 0, y: 60 }}
|
||||
>
|
||||
{(item) => ({opacity, y}) => (
|
||||
<animated.div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
transform: y.interpolate(y => `translate3d(0, ${y}%, 0)`),
|
||||
opacity
|
||||
}}
|
||||
>
|
||||
{this.props.children[currentIndex]}
|
||||
</animated.div>
|
||||
)}
|
||||
</Transition>
|
||||
<style jsx>
|
||||
{`
|
||||
{
|
||||
width: 100%;
|
||||
height: 3.4em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
//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...');
|
||||
|
||||
this.messaging.requestPermission().then(function () {
|
||||
console.log('Notification permission granted.');
|
||||
// TODO(developer): Retrieve an Instance ID token for use with FCM.
|
||||
// [START_EXCLUDE]
|
||||
// In many cases once an app has been granted notification permission, it
|
||||
// should update its UI reflecting this.
|
||||
// resetUI();
|
||||
// [END_EXCLUDE]
|
||||
}).catch(function (err) {
|
||||
console.log('Unable to get permission to notify.', err);
|
||||
});
|
||||
}
|
||||
|
||||
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.');
|
||||
this.requestPermission()
|
||||
// Show permission UI.
|
||||
// updateUIForPushPermissionRequired();
|
||||
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,
|
||||
pwa: true,
|
||||
boats: false
|
||||
});
|
||||
|
||||
this.setTokenSentToServer(true);
|
||||
} else {
|
||||
console.log('Token already sent to server so won\'t send it again ' +
|
||||
'unless it changes');
|
||||
}
|
||||
}
|
||||
async isTokenSentToServer() {
|
||||
return window.localStorage.getItem('sentToServer') === '1';
|
||||
}
|
||||
async setTokenSentToServer(sent) {
|
||||
window.localStorage.setItem('sentToServer', sent ? '1' : '0');
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new Singleton()
|
||||
Object.freeze(instance)
|
||||
|
||||
export default instance
|
||||
@@ -0,0 +1,121 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import withStyles from '@material-ui/core/styles/withStyles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import PropTypes from 'prop-types';
|
||||
import InputAdornment from '@material-ui/core/InputAdornment';
|
||||
|
||||
import TextRotationNone from '@material-ui/icons/TextRotationNone';
|
||||
import Subtitles from '@material-ui/icons/Subtitles';
|
||||
import LinkIcon from '@material-ui/icons/Link';
|
||||
import ImageIcon from '@material-ui/icons/Image';
|
||||
|
||||
|
||||
const styles = theme => ({
|
||||
input: {
|
||||
position: 'relative',
|
||||
},
|
||||
button: {
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
class PushParamForm extends Component {
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Fragment>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Créer votre message
|
||||
</Typography>
|
||||
<Grid container spacing={24}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<TextField
|
||||
value={this.props.title} InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<TextRotationNone />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
required id="title" label="Titre" fullWidth onChange={(event) => this.props.onSelectedTitle(event.target.value)} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<TextField value={this.props.body}
|
||||
InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<Subtitles />
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
required id="text" label="Body" fullWidth onChange={(event) => this.props.onSelectedBody(event.target.value)} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<TextField
|
||||
placeholder="https://"
|
||||
value={this.props.action} InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<LinkIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}} required id="link" label="Action link" fullWidth onChange={(event) => this.props.onSelectedAction(event.target.value)} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<TextField
|
||||
placeholder="https://"
|
||||
value={this.props.iconLink} InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<ImageIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}} required id="IconLink" label="Icon link" fullWidth onChange={(event) => this.props.onSelectedIconLink(event.target.value)} />
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<TextField
|
||||
placeholder="https://"
|
||||
value={this.props.imageLink} InputProps={{
|
||||
startAdornment: (
|
||||
<InputAdornment position="start">
|
||||
<ImageIcon />
|
||||
</InputAdornment>
|
||||
),
|
||||
}} required id="ImageLink" label="Image link" fullWidth onChange={(event) => this.props.onSelectedImageLink(event.target.value)} />
|
||||
</Grid>
|
||||
{/* <Grid item xs={12} md={6}>
|
||||
<input
|
||||
accept="image/*"
|
||||
className={classes.input}
|
||||
id="contained-button-file"
|
||||
style={{ display: 'none' }}
|
||||
type="file"
|
||||
accept="image/x-png,image/gif,image/jpeg"
|
||||
/>
|
||||
<label htmlFor="contained-button-file">
|
||||
<Button variant="contained" component="span" className={classes.button}>
|
||||
Upload picture
|
||||
</Button>
|
||||
</label>
|
||||
</Grid> */}
|
||||
</Grid>
|
||||
</Fragment>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PushParamForm.propTypes = {
|
||||
onSelectedTitle: PropTypes.func.isRequired,
|
||||
onSelectedBody: PropTypes.func.isRequired,
|
||||
onSelectedAction: PropTypes.func.isRequired,
|
||||
onSelectedImageLink: PropTypes.func.isRequired,
|
||||
onSelectedIconLink: PropTypes.func.isRequired,
|
||||
}
|
||||
|
||||
export default withStyles(styles)(PushParamForm);
|
||||
@@ -0,0 +1,68 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import withStyles from '@material-ui/core/styles/withStyles';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const styles = theme => ({
|
||||
title: {
|
||||
position: 'relative',
|
||||
},
|
||||
maxImageSize:
|
||||
{
|
||||
maxWidth:'540px',
|
||||
maxHeight:'180px'
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
class PushReview extends Component {
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<Grid container spacing={16}>
|
||||
<Grid item xs={12} sm={6}>
|
||||
<Typography variant="h6" gutterBottom className={classes.title}>
|
||||
Prêt à envoyer
|
||||
</Typography> <Typography variant="h6" gutterBottom className={classes.title}>
|
||||
{this.props.target === 'a' ? "A moi" : "A tous les inscrits"}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item container direction="column" xs={12} sm={12}>
|
||||
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h6" gutterBottom className={classes.title}>
|
||||
{this.props.title}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<Typography variant="h6" gutterBottom className={classes.title}>
|
||||
{this.props.body}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<Typography variant="h6" gutterBottom className={classes.title}>
|
||||
{this.props.action}
|
||||
</Typography>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
<img src={this.props.iconLink} alt="icon" className={classes.maxImageSize}></img>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<img src={this.props.imageLink} alt="image" className={classes.maxImageSize}></img>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(PushReview);
|
||||
|
||||
@@ -0,0 +1,140 @@
|
||||
import React, { Component, Fragment } from 'react'
|
||||
import withStyles from '@material-ui/core/styles/withStyles';
|
||||
import { Text } from '@vx/text';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Grid from '@material-ui/core/Grid';
|
||||
import Radio from '@material-ui/core/Radio';
|
||||
import RadioGroup from '@material-ui/core/RadioGroup';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormLabel from '@material-ui/core/FormLabel';
|
||||
import InputLabel from '@material-ui/core/InputLabel';
|
||||
import Select from '@material-ui/core/Select';
|
||||
import MenuItem from '@material-ui/core/MenuItem';
|
||||
import PropTypes from 'prop-types';
|
||||
import getConfig from 'next/config'
|
||||
|
||||
const {publicRuntimeConfig} = getConfig()
|
||||
const styles = theme => ({
|
||||
title: {
|
||||
position: 'relative',
|
||||
}, formControl: {
|
||||
margin: theme.spacing.unit,
|
||||
minWidth: 120,
|
||||
},
|
||||
selectEmpty: {
|
||||
marginTop: theme.spacing.unit * 2,
|
||||
},
|
||||
})
|
||||
|
||||
class PushTargetForm extends Component {
|
||||
state = {
|
||||
open: false
|
||||
}
|
||||
|
||||
handleChangeSelect = event => {
|
||||
this.props.onSelectedTopic(event.target.value);
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ open: false });
|
||||
};
|
||||
|
||||
handleOpen = () => {
|
||||
this.setState({ open: true });
|
||||
};
|
||||
|
||||
handleChange = event => {
|
||||
|
||||
this.setState({ open: event.target.value === 'c' && this.props.topic === '' });
|
||||
|
||||
this.props.onSelectedTarget(event.target.value);
|
||||
};
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const topics = publicRuntimeConfig.push.topics
|
||||
return (
|
||||
<Fragment>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
<p>Choisir une cible</p>
|
||||
</Typography>
|
||||
<Grid container spacing={24}>
|
||||
<Grid item xs={12} md={12}>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Cible</FormLabel>
|
||||
<RadioGroup
|
||||
aria-label="position"
|
||||
name="position"
|
||||
|
||||
onChange={this.handleChange}
|
||||
row
|
||||
>
|
||||
<FormControlLabel
|
||||
value="top"
|
||||
control={<Radio
|
||||
checked={this.props.target === 'a'}
|
||||
onChange={this.handleChange}
|
||||
value="a"
|
||||
name="radio-button-demo"
|
||||
aria-label="A"
|
||||
|
||||
/>}
|
||||
label="A moi"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="start"
|
||||
control={<Radio
|
||||
checked={this.props.target === 'b'}
|
||||
onChange={this.handleChange}
|
||||
value="b"
|
||||
name="radio-button-demo"
|
||||
aria-label="B"
|
||||
|
||||
/>}
|
||||
label="A tous les inscrits"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
<FormControlLabel
|
||||
value="start"
|
||||
control={<Radio
|
||||
checked={this.props.target === 'c'}
|
||||
onChange={this.handleChange}
|
||||
value="c"
|
||||
name="radio-button-demo"
|
||||
aria-label="C"
|
||||
|
||||
/>}
|
||||
label="Par sujet"
|
||||
labelPlacement="start"
|
||||
/>
|
||||
<FormControl className={classes.formControl} disabled={!(this.props.target === 'c')}>
|
||||
<InputLabel htmlFor="demo-controlled-open-select">Sujet</InputLabel>
|
||||
<Select
|
||||
open={this.state.open}
|
||||
onClose={this.handleClose}
|
||||
onOpen={this.handleOpen}
|
||||
value={this.props.topic}
|
||||
onChange={this.handleChangeSelect}
|
||||
|
||||
>
|
||||
{topics.map((item)=> {return <MenuItem value={item.key}>{item.key}</MenuItem>})}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</RadioGroup>
|
||||
</FormControl>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
PushTargetForm.propTypes = {
|
||||
onSelectedTarget: PropTypes.func.isRequired,
|
||||
onSelectedTopic: PropTypes.func.isRequired,
|
||||
target: PropTypes.string.isRequired,
|
||||
topic: PropTypes.string.isRequired
|
||||
|
||||
}
|
||||
export default withStyles(styles)(PushTargetForm);
|
||||
@@ -0,0 +1,111 @@
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import { Row, Col, Form, Input, Label, Button } from 'reactstrap'
|
||||
import Cookies from 'universal-cookie'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
email: '',
|
||||
session: this.props.session,
|
||||
providers: this.props.providers,
|
||||
submitting: false
|
||||
}
|
||||
this.handleSubmit = this.handleSubmit.bind(this)
|
||||
this.handleEmailChange = this.handleEmailChange.bind(this)
|
||||
|
||||
}
|
||||
|
||||
handleEmailChange(event) {
|
||||
this.setState({
|
||||
email: event.target.value.trim()
|
||||
})
|
||||
}
|
||||
|
||||
handleSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
if (!this.state.email) return
|
||||
|
||||
this.setState({
|
||||
submitting: true
|
||||
})
|
||||
|
||||
// Save current URL so user is redirected back here after signing in
|
||||
const cookies = new Cookies()
|
||||
cookies.set('redirect_url', window.location.pathname, { path: '/' })
|
||||
|
||||
NextAuth.signin(this.state.email)
|
||||
.then(() => {
|
||||
Router.push(`/auth/check-email?email=${this.state.email}`)
|
||||
})
|
||||
.catch(err => {
|
||||
Router.push(`/auth/error?action=signin&type=email&email=${this.state.email}`)
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.session.user) {
|
||||
return (<div />)
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<p className="text-center" style={{ marginTop: 10, marginBottom: 30 }}>{`If you don't have an account, one will be created when you sign in.`}</p>
|
||||
<Row>
|
||||
<Col xs={12} md={6}>
|
||||
<SignInButtons providers={this.props.providers} />
|
||||
</Col>
|
||||
<Col xs={12} md={6}>
|
||||
<Form id="signin" method="post" action="/auth/email/signin" onSubmit={this.handleSubmit}>
|
||||
<Input name="_csrf" type="hidden" value={this.state.session.csrfToken} />
|
||||
<p>
|
||||
<Label htmlFor="email">Email address</Label><br />
|
||||
<Input name="email" disabled={this.state.submitting} type="text" placeholder="j.smith@example.com" id="email" className="form-control" value={this.state.email} onChange={this.handleEmailChange} />
|
||||
</p>
|
||||
<p className="text-right">
|
||||
<Button id="submitButton" disabled={this.state.submitting} outline color="dark" type="submit">
|
||||
{this.state.submitting === true && <span className="icon icon-spin ion-md-refresh mr-2" />}
|
||||
Sign in with email
|
||||
</Button>
|
||||
</p>
|
||||
</Form>
|
||||
</Col>
|
||||
|
||||
|
||||
<Col xs={12} md={6}>
|
||||
<p className="text-center small">
|
||||
<Link href="/auth/credentials"><a>Sign in with credentials</a></Link>
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</React.Fragment >
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SignInButtons extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
Object.keys(this.props.providers).map((provider, i) => {
|
||||
if (!this.props.providers[provider].signin) return null
|
||||
|
||||
return (
|
||||
<p key={i}>
|
||||
<a className="btn btn-block btn-outline-secondary" href={this.props.providers[provider].signin}>
|
||||
Sign in with {provider}
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
})
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
|
||||
import Layout from '../components/MyLayout.js'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import { Spring, animated } from 'react-spring'
|
||||
import { interpolate, interpolateAll, separate } from 'flubber'
|
||||
import { GradientPinkRed as Gradient } from '@vx/gradient'
|
||||
import { Text } from '@vx/text';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
},
|
||||
});
|
||||
|
||||
// 'bin2': 'M192 1024h640l64-704h-768zM640 128v-128h-256v128h-320v192l64-64h768l64 64v-192h-320zM576 128h-128v-64h128v64z',
|
||||
// 'bold': 'M707.88 484.652c37.498-44.542 60.12-102.008 60.12-164.652 0-141.16-114.842-256-256-256h-320v896h384c141.158 0 256-114.842 256-256 0-92.956-49.798-174.496-124.12-219.348zM384 192h101.5c55.968 0 101.5 57.42 101.5 128s-45.532 128-101.5 128h-101.5v-256zM543 832h-159v-256h159c58.45 0 106 57.42 106 128s-47.55 128-106 128z',
|
||||
// 'underline': 'M704 64h128v416c0 159.058-143.268 288-320 288-176.73 0-320-128.942-320-288v-416h128v416c0 40.166 18.238 78.704 51.354 108.506 36.896 33.204 86.846 51.494 140.646 51.494s103.75-18.29 140.646-51.494c33.116-29.802 51.354-68.34 51.354-108.506v-416zM192 832h640v128h-640z',
|
||||
// 'italic': 'M896 64v64h-128l-320 768h128v64h-448v-64h128l320-768h-128v-64z',
|
||||
// 'paragraph-left': 'M0 64h1024v128h-1024zM0 256h640v128h-640zM0 640h640v128h-640zM0 448h1024v128h-1024zM0 832h1024v128h-1024z',
|
||||
// 'paragraph-center': 'M0 64h1024v128h-1024zM192 256h640v128h-640zM192 640h640v128h-640zM0 448h1024v128h-1024zM0 832h1024v128h-1024z',
|
||||
// 'paragraph-right': 'M0 64h1024v128h-1024zM384 256h640v128h-640zM384 640h640v128h-640zM0 448h1024v128h-1024zM0 832h1024v128h-1024z',
|
||||
// 'google': 'M522.2 438.8v175.6h290.4c-11.8 75.4-87.8 220.8-290.4 220.8-174.8 0-317.4-144.8-317.4-323.2s142.6-323.2 317.4-323.2c99.4 0 166 42.4 204 79l139-133.8c-89.2-83.6-204.8-134-343-134-283 0-512 229-512 512s229 512 512 512c295.4 0 491.6-207.8 491.6-500.2 0-33.6-3.6-59.2-8-84.8l-483.6-0.2z',
|
||||
// 'facebook': 'M608 192h160v-192h-160c-123.514 0-224 100.486-224 224v96h-128v192h128v512h192v-512h160l32-192h-192v-96c0-17.346 14.654-32 32-32z',
|
||||
// 'twitter': 'M1024 226.4c-37.6 16.8-78.2 28-120.6 33 43.4-26 76.6-67.2 92.4-116.2-40.6 24-85.6 41.6-133.4 51-38.4-40.8-93-66.2-153.4-66.2-116 0-210 94-210 210 0 16.4 1.8 32.4 5.4 47.8-174.6-8.8-329.4-92.4-433-219.6-18 31-28.4 67.2-28.4 105.6 0 72.8 37 137.2 93.4 174.8-34.4-1-66.8-10.6-95.2-26.2 0 0.8 0 1.8 0 2.6 0 101.8 72.4 186.8 168.6 206-17.6 4.8-36.2 7.4-55.4 7.4-13.6 0-26.6-1.4-39.6-3.8 26.8 83.4 104.4 144.2 196.2 146-72 56.4-162.4 90-261 90-17 0-33.6-1-50.2-3 93.2 59.8 203.6 94.4 322.2 94.4 386.4 0 597.8-320.2 597.8-597.8 0-9.2-0.2-18.2-0.6-27.2 41-29.4 76.6-66.4 104.8-108.6z',
|
||||
// 'linkedin2': 'M384 384h177.106v90.782h2.532c24.64-44.194 84.958-90.782 174.842-90.782 186.946 0 221.52 116.376 221.52 267.734v308.266h-184.61v-273.278c0-65.184-1.334-149.026-96.028-149.026-96.148 0-110.82 70.986-110.82 144.292v278.012h-184.542v-576z M64 384h192v576h-192v-576z M256 224c0 53.019-42.981 96-96 96s-96-42.981-96-96c0-53.019 42.981-96 96-96s96 42.981 96 96z',
|
||||
|
||||
|
||||
class Image extends React.Component {
|
||||
|
||||
//TODO:https://github.com/veltman/flubber
|
||||
// for multi shape interpolation
|
||||
state = {
|
||||
paths: [
|
||||
['M 4.1011306,18.138058 C 2.331559,17.122773 2.6024383,14.859921 4.5707888,14.214339 c 1.1026388,-0.361698 1.9711507,-0.199331 2.8110534,0.525296 0.8690356,0.74987 1.0374015,1.48815 0.5534758,2.427702 -0.6579674,1.277374 -2.4908068,1.741422 -3.8341874,0.970721 z '
|
||||
, 'M 10.631851,15.182299 C 9.1656813,14.751768 8.7340471,13.059324 9.8467661,12.103776 10.527925,11.51888 11.447639,11.385214 12.326604,11.743318 c 0.788932,0.321555 1.190552,0.91555 1.190552,1.76115 0,0.689726 -0.633869,1.48443 -1.318177,1.653143 -0.820425,0.201948 -0.955486,0.204293 -1.567128,0.0247 z '
|
||||
, 'M 4.7521177,13.02856 C 2.9082762,12.471124 2.3313006,10.653967 3.603096,9.4101092 4.695148,8.3419987 6.4288405,8.3355162 7.5124752,9.3942534 8.285595,10.150333 8.3999412,10.893721 7.8808416,11.788709 7.2663072,12.848273 5.9207259,13.381011 4.7521177,13.027878 Z '
|
||||
, 'M 15.388892,12.283788 c -0.58348,-0.202084 -0.968125,-0.775943 -0.968125,-1.444286 0,-1.5622262 2.510542,-2.1126367 3.341497,-0.732767 0.838771,1.392834 -0.662497,2.769942 -2.373372,2.177053 z '
|
||||
, 'M 10.24832,9.9111326 C 9.9120272,9.7166264 9.5352364,9.3847324 9.4110101,9.1740924 8.5785356,7.7613854 10.263526,6.053759 12.003506,6.5467794 c 1.831736,0.5192301 2.068755,2.7702231 0.364795,3.4646396 -0.846384,0.344859 -1.395814,0.318794 -2.119981,-0.1002864 z '
|
||||
, 'M 4.7709634,7.6206842 C 4.0603393,7.4381829 3.2673775,6.7563237 3.0215248,6.116808 2.3948596,4.4861553 4.2885421,2.8190798 6.2516733,3.2729259 7.6353145,3.5929599 8.506468,4.9099156 8.0846798,6.0438273 7.8009787,6.8068061 7.0880867,7.4384634 6.2985231,7.6270307 5.5598051,7.8031859 5.4791529,7.8027691 4.7709634,7.6201555 Z'],
|
||||
['M11.344,5.71c0-0.73,0.074-1.122,1.199-1.122h1.502V1.871h-2.404c-2.886,0-3.903,1.36-3.903,3.646v1.765h-1.8V10h1.8v8.128h3.601V10h2.403l0.32-2.718h-2.724L11.344,5.71z'
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"],
|
||||
['M18.258,3.266c-0.693,0.405-1.46,0.698-2.277,0.857c-0.653-0.686-1.586-1.115-2.618-1.115c-1.98,0-3.586,1.581-3.586,3.53c0,0.276,0.031,0.545,0.092,0.805C6.888,7.195,4.245,5.79,2.476,3.654C2.167,4.176,1.99,4.781,1.99,5.429c0,1.224,0.633,2.305,1.596,2.938C2.999,8.349,2.445,8.19,1.961,7.925C1.96,7.94,1.96,7.954,1.96,7.97c0,1.71,1.237,3.138,2.877,3.462c-0.301,0.08-0.617,0.123-0.945,0.123c-0.23,0-0.456-0.021-0.674-0.062c0.456,1.402,1.781,2.422,3.35,2.451c-1.228,0.947-2.773,1.512-4.454,1.512c-0.291,0-0.575-0.016-0.855-0.049c1.588,1,3.473,1.586,5.498,1.586c6.598,0,10.205-5.379,10.205-10.045c0-0.153-0.003-0.305-0.01-0.456c0.7-0.499,1.308-1.12,1.789-1.827c-0.644,0.28-1.334,0.469-2.06,0.555C17.422,4.782,17.99,4.091,18.258,3.266'
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"],
|
||||
['m 7.9524801,6.8649712 h 4.4013439 v 2.4344997 h 0.06282 c 0.612359,-1.1856702 2.111339,-2.4344997 4.345083,-2.4344997 4.645877,0 5.505101,3.1207251 5.505101,7.1797098 v 8.266623 h -4.587857 v -7.328365 c 0,-1.747966 -0.03217,-3.996275 -2.386436,-3.996275 -2.389421,0 -2.754033,1.903479 -2.754033,3.869364 v 7.455276 H 7.9523576 Z'
|
||||
, 'M 0,6.8649712 H 4.7714878 V 22.311304 H 0 Z '
|
||||
, 'M 4.7714878,2.5743644 c 0,1.4210168 -1.0681544,2.5743638 -2.3857592,2.5743638 C 1.0681544,5.1487282 0,3.9958278 0,2.5743644 0,1.1533476 1.0680012,0 2.3857286,0 3.7033029,0 4.7714878,1.1529004 4.7714878,2.5743644 Z'
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
, "M0,0 L0.01,0 L0.01,0.01Z"
|
||||
],
|
||||
|
||||
],
|
||||
|
||||
links: [
|
||||
'https://www.talentstube.com/entreprise/5-e-declic/',
|
||||
'https://www.facebook.com/edeclic/',
|
||||
'https://twitter.com/edeclic',
|
||||
'https://www.linkedin.com/company/edeclic/',
|
||||
//
|
||||
],
|
||||
|
||||
index: 0
|
||||
}
|
||||
goNext = () => this.setState(state => ({ index: state.index + 1 >= state.paths.length ? 0 : state.index + 1 }))
|
||||
render() {
|
||||
const { paths, links, index } = this.state
|
||||
|
||||
const interpolator = interpolateAll(paths[index], paths[index + 1] || paths[0], { maxSegmentLength: 0.1, single: true })
|
||||
// const interpolatorR = separate(paths[0], paths[2] || paths[0], { single: true })
|
||||
return (
|
||||
<svg width={this.props.svgWidth} viewBox="0 0 22 22" style={{margin: 'auto'}}>
|
||||
<Gradient id="gradient" />
|
||||
<g fill="url(#gradient)">
|
||||
<a href={links[index]} target='_blank'>
|
||||
<Spring reset native from={{ t: 0 }} to={{ t: 1 }} onRest={this.goNext} delay={this.props.delay}>
|
||||
{({ t }) => <animated.path d={t.interpolate(interpolator)} ></animated.path>}
|
||||
|
||||
</Spring>
|
||||
</a>
|
||||
</g>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Image.defaultProps = {
|
||||
delay: '500',
|
||||
svgWidth: '100'
|
||||
}
|
||||
|
||||
Image.propTypes = {
|
||||
delay: PropTypes.string.isRequired,
|
||||
svgWidth: PropTypes.string.isRequired,
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Image)
|
||||
@@ -0,0 +1,29 @@
|
||||
curl -X POST -H "Authorization: Bearer ya29.c.ElpvBqGmaItXdsYUwpgjzQoZBGzAdmkD1S-iUZBmZ0Yxt_hiCw32TdudGjaXu-nPn3-crDtj0tw54UDk0zgxSa3rSKA82HC-bc9BZDsS-4xh7W6m4BfYsFvI07I" -H "Content-Type: application/json" -d '{
|
||||
"message":{
|
||||
"webpush": {
|
||||
"headers": {
|
||||
"Urgency": "high"
|
||||
},
|
||||
"notification": {
|
||||
"body": "notification web",
|
||||
"requireInteraction": "true",
|
||||
"icon":"http://localhost:8000/static/images/icons/e-declicIco128x128.png",
|
||||
"image": "http://localhost:8000/static/images/icons/e-declicIco128x128.png"
|
||||
},
|
||||
},
|
||||
"token": "f7gCuQGRQ6I:APA91bEGGUKCUBbVlxSFQbL4zL6ishb6MJzmqLEh1Tiwl-gewgl8wDzjBeUv5AJb9kAm8McbWj8EhkBTbIobJkeeUS4weLIQtSxb44NFNYdwDP8ifKaq6AXp-Bx-PZeOvJ5uE5bntkKU"
|
||||
}
|
||||
}' https://fcm.googleapis.com/v1/projects/edeclicpwa/messages:send
|
||||
|
||||
curl -X POST -H "Authorization:key=AAAAHlF5rsI:APA91bHXvjGDJRLzVvqmBDPM1Et96Bw0Iuvc7EGe9wt4a_lSJijDlbZ3RT96seLDK-QM6Mf8qQ4dH6MI9iWt1_d3uzKG2bxr56zltglZ3Z9xU8gz2iJeRGmlOd0YdV43IumIVe6dIOr-" https://iid.googleapis.com/iid/v1/efrEbYvXcR0:APA91bFvWQS57rXVUF68Uz-rGljwYD2WnKCRbOWFSsNyazG6MM08iztjtQZK4BZJPd8XPo0CNecYGcetBjOn6pjl1LSepzCeZucSS4TEpO2PNpl3RNJTVSLIi9J9eU-Dm-HeJbd_Quye/rel/topics/testse -H "Content-Type: application/json"
|
||||
|
||||
|
||||
curl -X POST -H "Authorization:key=AAAAHlF5rsI:APA91bHXvjGDJRLzVvqmBDPM1Et96Bw0Iuvc7EGe9wt4a_lSJijDlbZ3RT96seLDK-QM6Mf8qQ4dH6MI9iWt1_d3uzKG2bxr56zltglZ3Z9xU8gz2iJeRGmlOd0YdV43IumIVe6dIOr-" https://iid.googleapis.com/iid/v1/dWt_RrUJSos:APA91bHLFxW4s2yXpLYeR3mz_WJGQDIdrbO0XkFelSFbZI_146s46iYPQUeAKut_VThfB79P8xmR0ezGR6JOTzRQmi_SBl7q_c-9LUx1gj840Of7N4aaNf_HbyZhjiG3UJTBx8Klftk9/rel/topics/testse -H "Content-Type: application/json"
|
||||
|
||||
|
||||
{"payload":{"notification":{"title":"FCM Message","body":"This is an FCM Message"},"webpush":{"headers":{"Urgency":"high"},"notification":{"body":"This is a message from FCM to web","icon":"http://localhost:8000/static/images/icons/e-declicIco128x128.png","image":"http://localhost:8000/static/images/icons/e-declicIco128x128.png"}}}}
|
||||
|
||||
|
||||
https://us-central1-edeclicpwa.cloudfunctions.net/pushFixedMessage
|
||||
|
||||
curl -X POST -H "Authorization:key=AAAAHlF5rsI:APA91bHXvjGDJRLzVvqmBDPM1Et96Bw0Iuvc7EGe9wt4a_lSJijDlbZ3RT96seLDK-QM6Mf8qQ4dH6MI9iWt1_d3uzKG2bxr56zltglZ3Z9xU8gz2iJeRGmlOd0YdV43IumIVe6dIOr-" https://iid.googleapis.com/iid/info/f7gCuQGRQ6I:APA91bEGGUKCUBbVlxSFQbL4zL6ishb6MJzmqLEh1Tiwl-gewgl8wDzjBeUv5AJb9kAm8McbWj8EhkBTbIobJkeeUS4weLIQtSxb44NFNYdwDP8ifKaq6AXp-Bx-PZeOvJ5uE5bntkKU?details=true -H "Content-Type: application/json"
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1,34 @@
|
||||
describe('home', () => {
|
||||
it('should assert that root / is up', () => {
|
||||
cy.visit('http://localhost:8000')
|
||||
// cy.get('#Title')
|
||||
// .should('have.text', 'Nous sommes une agence web');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Conseil', () => {
|
||||
it('should assert that /qui-sommes-nous is up', () => {
|
||||
cy.visit('http://localhost:8000/qui-sommes-nous')
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('solutions', () => {
|
||||
it('should assert that /solutions is up', () => {
|
||||
cy.visit('http://localhost:8000/solutions')
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('blog', () => {
|
||||
it('should assert that /blog is up', () => {
|
||||
cy.visit('http://localhost:8000/blog')
|
||||
});
|
||||
});
|
||||
|
||||
describe('contact', () => {
|
||||
it('should assert that /contact is up', () => {
|
||||
cy.visit('http://localhost:8000/contact')
|
||||
cy.get('a').should('have.attr', 'href', 'http://www.e-declic.com')
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
|
||||
/**
|
||||
* Welcome to your Workbox-powered service worker!
|
||||
*
|
||||
* You'll need to register this file in your web app and you should
|
||||
* disable HTTP caching for this file too.
|
||||
* See https://goo.gl/nhQhGp
|
||||
*
|
||||
* The rest of the code is auto-generated. Please don't update this file
|
||||
* directly; instead, make changes to your Workbox build configuration
|
||||
* and re-run your build process.
|
||||
* See https://goo.gl/2aRDsh
|
||||
*/
|
||||
|
||||
importScripts("https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");
|
||||
|
||||
importScripts(
|
||||
"/static/js/firebase-messaging-sw.js",
|
||||
"/static/js/backgroundSync-sw.js"
|
||||
);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'pwa',
|
||||
port: 8000,
|
||||
script: 'yarn start',
|
||||
cwd: '/home/app',
|
||||
env: {
|
||||
NODE_ENV: 'development'
|
||||
},
|
||||
env_production: {
|
||||
NODE_ENV: 'production'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* next-auth.config.js Example
|
||||
*
|
||||
* Environment variables for this example:
|
||||
*
|
||||
* PORT=3000
|
||||
* SERVER_URL=http://localhost:3000
|
||||
* MONGO_URI=mongodb://localhost:27017/my-database
|
||||
*
|
||||
* If you wish, you can put these in a `.env` to seperate your environment
|
||||
* specific configuration from your code.
|
||||
**/
|
||||
|
||||
// Load environment variables from a .env file if one exists
|
||||
require('dotenv').load()
|
||||
|
||||
const nextAuthProviders = require('./next-auth.providers')
|
||||
const nextAuthFunctions = require('./next-auth.functions')
|
||||
|
||||
// If we want to pass a custom session store then we also need to pass an
|
||||
// instance of Express Session along with it.
|
||||
const expressSession = require('express-session')
|
||||
const MongoStore = require('connect-mongo')(expressSession)
|
||||
|
||||
// If no store set, NextAuth defaults to using Express Sessions in-memory
|
||||
// session store (the fallback is intended as fallback for testing only).
|
||||
let sessionStore
|
||||
if (process.env.MONGO_URI) {
|
||||
sessionStore = new MongoStore({
|
||||
url: process.env.MONGO_URI,
|
||||
autoRemove: 'interval',
|
||||
autoRemoveInterval: 10, // Removes expired sessions every 10 minutes
|
||||
collection: 'sessions',
|
||||
stringify: false
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = () => {
|
||||
// We connect to the User DB before we define our functions.
|
||||
// next-auth.functions.js returns an async method that does that and returns
|
||||
// an object with the functions needed for authentication.
|
||||
return nextAuthFunctions()
|
||||
.then(functions => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// This is the config block we return, ready to be passed to NextAuth
|
||||
resolve({
|
||||
// Define a port (if none passed, will not start Express)
|
||||
// Note: This project omits a port for NextAuth as it uses Express to
|
||||
// add additional routes for the examples, so it takes control of
|
||||
// starting Express, rather than leaving it to NextAuth.
|
||||
// port: process.env.PORT || 3000,
|
||||
// Secret used to encrypt session data on the server.
|
||||
sessionSecret: 'pwacrypretenonsecrettedsecsessi',
|
||||
// Maximum Session Age in ms (optional, default is 7 days).
|
||||
// The expiry time for a session is reset every time a user revisits
|
||||
// the site or revalidates their session token. This is the maximum
|
||||
// idle time value.
|
||||
sessionMaxAge: 60000 * 60 * 24 * 7,
|
||||
// Session Revalidation in X ms (optional, default is 60 seconds).
|
||||
// Specifies how often a Single Page App should revalidate a session.
|
||||
// Does not impact the session life on the server, but causes clients
|
||||
// to refetch session info (even if it is in a local cache) after N
|
||||
// seconds has elapsed since it was last checked so they always display
|
||||
// state correctly.
|
||||
// If set to 0 will revalidate a session before rendering every page.
|
||||
sessionRevalidateAge: 60000,
|
||||
// Canonical URL of the server (optiona, but recommended).
|
||||
// e.g. 'http://localhost:3000' or 'https://www.example.com'
|
||||
// Used in callbak URLs and email sign in links. It will be auto
|
||||
// generated if not specified, which may cause problems if your site
|
||||
// uses multiple aliases (e.g. 'example.com and 'www.examples.com').
|
||||
serverUrl: process.env.SERVER_URL || null,
|
||||
// Add an Express Session store.
|
||||
expressSession: expressSession,
|
||||
sessionStore: sessionStore,
|
||||
// Define oAuth Providers
|
||||
providers: nextAuthProviders(),
|
||||
// Define functions for manging users and sending email.
|
||||
functions: functions,
|
||||
|
||||
csrf:{whitelist:['/account/user']}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* next-auth.functions.js Example
|
||||
*
|
||||
* This file defines functions NextAuth to look up, add and update users.
|
||||
*
|
||||
* It returns a Promise with the functions matching these signatures:
|
||||
*
|
||||
* {
|
||||
* find: ({
|
||||
* id,
|
||||
* email,
|
||||
* emailToken,
|
||||
* provider,
|
||||
* poviderToken
|
||||
* } = {}) => {},
|
||||
* update: (user) => {},
|
||||
* insert: (user) => {},
|
||||
* remove: (id) => {},
|
||||
* serialize: (user) => {},
|
||||
* deserialize: (id) => {}
|
||||
* }
|
||||
*
|
||||
* Each function returns Promise.resolve() - or Promise.reject() on error.
|
||||
*
|
||||
* This specific example supports both MongoDB and NeDB, but can be refactored
|
||||
* to work with any database.
|
||||
*
|
||||
* Environment variables for this example:
|
||||
*
|
||||
* MONGO_URI=mongodb://localhost:27017/my-database
|
||||
* EMAIL_FROM=username@gmail.com
|
||||
* EMAIL_SERVER=smtp.gmail.com
|
||||
* EMAIL_PORT=465
|
||||
* EMAIL_USERNAME=username@gmail.com
|
||||
* EMAIL_PASSWORD=p4ssw0rd
|
||||
*
|
||||
* If you wish, you can put these in a `.env` to seperate your environment
|
||||
* specific configuration from your code.
|
||||
**/
|
||||
|
||||
// Load environment variables from a .env file if one exists
|
||||
require('dotenv').load()
|
||||
|
||||
// This config file uses MongoDB for User accounts, as well as session storage.
|
||||
// This config includes options for NeDB, which it defaults to if no DB URI
|
||||
// is specified. NeDB is an in-memory only database intended here for testing.
|
||||
const MongoClient = require('mongodb').MongoClient
|
||||
const NeDB = require('nedb')
|
||||
const MongoObjectId = (process.env.MONGO_URI) ? require('mongodb').ObjectId : (id) => { return id }
|
||||
|
||||
// Use Node Mailer for email sign in
|
||||
const nodemailer = require('nodemailer')
|
||||
const nodemailerSmtpTransport = require('nodemailer-smtp-transport')
|
||||
const nodemailerDirectTransport = require('nodemailer-direct-transport')
|
||||
|
||||
// Send email direct from localhost if no mail server configured
|
||||
let nodemailerTransport = nodemailerDirectTransport()
|
||||
if (process.env.EMAIL_SERVER && process.env.EMAIL_USERNAME && process.env.EMAIL_PASSWORD) {
|
||||
nodemailerTransport = nodemailerSmtpTransport({
|
||||
host: process.env.EMAIL_SERVER,
|
||||
port: process.env.EMAIL_PORT || 25,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USERNAME,
|
||||
pass: process.env.EMAIL_PASSWORD
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.env.MONGO_URI) {
|
||||
// Connect to MongoDB Database and return user connection
|
||||
MongoClient.connect(process.env.MONGO_URI, (err, mongoClient) => {
|
||||
if (err) return reject(err)
|
||||
const dbName = process.env.MONGO_URI.split('/').pop().split('?').shift()
|
||||
const db = mongoClient.db(dbName)
|
||||
return resolve(db.collection('users'))
|
||||
})
|
||||
} else {
|
||||
// If no MongoDB URI string specified, use NeDB, an in-memory work-a-like.
|
||||
// NeDB is not persistant and is intended for testing only.
|
||||
let collection = new NeDB({ autoload: true })
|
||||
collection.loadDatabase(err => {
|
||||
if (err) return reject(err)
|
||||
resolve(collection)
|
||||
})
|
||||
}
|
||||
})
|
||||
.then(usersCollection => {
|
||||
return Promise.resolve({
|
||||
// If a user is not found find() should return null (with no error).
|
||||
find: ({id, email, emailToken, provider} = {}) => {
|
||||
let query = {}
|
||||
|
||||
// Find needs to support looking up a user by ID, Email, Email Token,
|
||||
// and Provider Name + Users ID for that Provider
|
||||
if (id) {
|
||||
query = { _id: MongoObjectId(id) }
|
||||
} else if (email) {
|
||||
query = { email: email }
|
||||
} else if (emailToken) {
|
||||
query = { emailToken: emailToken }
|
||||
} else if (provider) {
|
||||
query = { [`${provider.name}.id`]: provider.id }
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
usersCollection.findOne(query, (err, user) => {
|
||||
if (err) return reject(err)
|
||||
return resolve(user)
|
||||
})
|
||||
})
|
||||
},
|
||||
// The user parameter contains a basic user object to be added to the DB.
|
||||
// The oAuthProfile parameter is passed when signing in via oAuth.
|
||||
//
|
||||
// The optional oAuthProfile parameter contains all properties associated
|
||||
// with the users account on the oAuth service they are signing in with.
|
||||
//
|
||||
// You can use this to capture profile.avatar, profile.location, etc.
|
||||
insert: (user, oAuthProfile) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
usersCollection.insert(user, (err, response) => {
|
||||
if (err) return reject(err)
|
||||
|
||||
// Mongo Client automatically adds an id to an inserted object, but
|
||||
// if using a work-a-like we may need to add it from the response.
|
||||
if (!user._id && response._id) user._id = response._id
|
||||
|
||||
return resolve(user)
|
||||
})
|
||||
})
|
||||
},
|
||||
// The user parameter contains a basic user object to be added to the DB.
|
||||
// The oAuthProfile parameter is passed when signing in via oAuth.
|
||||
//
|
||||
// The optional oAuthProfile parameter contains all properties associated
|
||||
// with the users account on the oAuth service they are signing in with.
|
||||
//
|
||||
// You can use this to capture profile.avatar, profile.location, etc.
|
||||
update: (user, profile) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
usersCollection.update({_id: MongoObjectId(user._id)}, user, {}, (err) => {
|
||||
if (err) return reject(err)
|
||||
return resolve(user)
|
||||
})
|
||||
})
|
||||
},
|
||||
// The remove parameter is passed the ID of a user account to delete.
|
||||
//
|
||||
// This method is not used in the current version of next-auth but will
|
||||
// be in a future release, to provide an endpoint for account deletion.
|
||||
remove: (id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
usersCollection.remove({_id: MongoObjectId(id)}, (err) => {
|
||||
if (err) return reject(err)
|
||||
return resolve(true)
|
||||
})
|
||||
})
|
||||
},
|
||||
// Seralize turns the value of the ID key from a User object
|
||||
serialize: (user) => {
|
||||
// Supports serialization from Mongo Object *and* deserialize() object
|
||||
if (user.id) {
|
||||
// Handle responses from deserialize()
|
||||
return Promise.resolve(user.id)
|
||||
} else if (user._id) {
|
||||
// Handle responses from find(), insert(), update()
|
||||
return Promise.resolve(user._id)
|
||||
} else {
|
||||
return Promise.reject(new Error("Unable to serialise user"))
|
||||
}
|
||||
},
|
||||
// Deseralize turns a User ID into a normalized User object that is
|
||||
// exported to clients. It should not return private/sensitive fields,
|
||||
// only fields you want to expose via the user interface.
|
||||
deserialize: (id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
usersCollection.findOne({ _id: MongoObjectId(id) }, (err, user) => {
|
||||
if (err) return reject(err)
|
||||
|
||||
// If user not found (e.g. account deleted) return null object
|
||||
if (!user) return resolve(null)
|
||||
|
||||
return resolve({
|
||||
id: user._id,
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
emailVerified: user.emailVerified,
|
||||
admin: user.admin || false
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
// Define method for sending links for signing in over email.
|
||||
sendSignInEmail: ({
|
||||
email = null,
|
||||
url = null
|
||||
} = {}) => {
|
||||
nodemailer
|
||||
.createTransport(nodemailerTransport)
|
||||
.sendMail({
|
||||
to: email,
|
||||
from: process.env.EMAIL_FROM,
|
||||
subject: 'Sign in link',
|
||||
text: `Use the link below to sign in:\n\n${url}\n\n`,
|
||||
html: `<p>Use the link below to sign in:</p><p>${url}</p>`
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
console.error('Error sending email to ' + email, err)
|
||||
}
|
||||
})
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('Generated sign in link ' + url + ' for ' + email)
|
||||
}
|
||||
},
|
||||
|
||||
// Credentials Sign In
|
||||
//
|
||||
// If you use this you will need to define your own way to validate
|
||||
// credentials. Unlike with oAuth or Email Sign In, accounts are not
|
||||
// created automatically so you will need to provide a way to create them.
|
||||
//
|
||||
// This feature is intended for strategies like Two Factor Authentication.
|
||||
//
|
||||
// To disable this option, do not set signin (or set it to null).
|
||||
/*
|
||||
*/
|
||||
signIn: ({form, req}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Should validate credentials (e.g. hash password, compare 2FA token
|
||||
// etc) and return a valid user object from a database.
|
||||
return usersCollection.findOne({
|
||||
email: form.email
|
||||
}, (err, user) => {
|
||||
if (err) return reject(err)
|
||||
if (!user) return resolve(null)
|
||||
|
||||
// Check credentials - e.g. compare bcrypt password hashes
|
||||
if (form.password === "test1234") {
|
||||
// If valid, return user object - e.g. { id, name, email }
|
||||
return resolve(user)
|
||||
} else {
|
||||
// If invalid, return null
|
||||
return resolve(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
// Session Object (optional)
|
||||
//
|
||||
// The session object that gets returned to the client. You don't need to
|
||||
// specify this function here unless you want to override or extend the
|
||||
// default (e.g. with any other properties you have added to req.session)
|
||||
//
|
||||
// Note: The object returned will be stored in localStorage and visible
|
||||
// client side so do not return data you would not want the user to see.
|
||||
/*
|
||||
session: (session, req) => {
|
||||
if (req.session && req.session.someCustomProperty)
|
||||
session.someCustomProperty = req.session.someCustomProperty
|
||||
|
||||
session.someOtherCustomProperty = "Example custom property"
|
||||
|
||||
return session
|
||||
}
|
||||
*/
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* next-auth.providers.js Example
|
||||
*
|
||||
* This file returns a simple array of oAuth Provider objects for NextAuth.
|
||||
*
|
||||
* This example returns an array based on what environment variables are set,
|
||||
* with explicit support for Facebook, Google and Twitter, but it can be used
|
||||
* to add strategies for other oAuth providers.
|
||||
*
|
||||
* Environment variables for this example:
|
||||
*
|
||||
* FACEBOOK_ID=
|
||||
* FACEBOOK_SECRET=
|
||||
* GOOGLE_ID=
|
||||
* GOOGLE_SECRET=
|
||||
* TWITTER_KEY=
|
||||
* TWITTER_SECRET=
|
||||
*
|
||||
* If you wish, you can put these in a `.env` to seperate your environment
|
||||
* specific configuration from your code.
|
||||
**/
|
||||
|
||||
// Load environment variables from a .env file if one exists
|
||||
require('dotenv').load()
|
||||
|
||||
module.exports = () => {
|
||||
let providers = []
|
||||
|
||||
if (process.env.FACEBOOK_ID && process.env.FACEBOOK_SECRET) {
|
||||
providers.push({
|
||||
providerName: 'Facebook',
|
||||
providerOptions: {
|
||||
scope: ['email', 'public_profile']
|
||||
},
|
||||
Strategy: require('passport-facebook').Strategy,
|
||||
strategyOptions: {
|
||||
clientID: process.env.FACEBOOK_ID,
|
||||
clientSecret: process.env.FACEBOOK_SECRET,
|
||||
profileFields: ['id', 'displayName', 'email', 'link']
|
||||
},
|
||||
getProfile(profile) {
|
||||
// Normalize profile into one with {id, name, email} keys
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.displayName,
|
||||
email: profile._json.email
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (process.env.LINKEDIN_ID && process.env.LINKEDIN_SECRET) {
|
||||
providers.push({
|
||||
providerName: 'Linkedin',
|
||||
providerOptions: {
|
||||
scope: ['r_emailaddress', 'r_basicprofile']
|
||||
},
|
||||
Strategy: require('passport-linkedin-oauth2').Strategy,
|
||||
strategyOptions: {
|
||||
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,
|
||||
// name: profile.displayName,
|
||||
// email: profile._json.email
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
if (process.env.GOOGLE_ID && process.env.GOOGLE_SECRET) {
|
||||
providers.push({
|
||||
providerName: 'Google',
|
||||
providerOptions: {
|
||||
scope: ['profile', 'email']
|
||||
},
|
||||
Strategy: require('passport-google-oauth').OAuth2Strategy,
|
||||
strategyOptions: {
|
||||
clientID: process.env.GOOGLE_ID,
|
||||
clientSecret: process.env.GOOGLE_SECRET
|
||||
},
|
||||
getProfile(profile) {
|
||||
// Normalize profile into one with {id, name, email} keys
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.displayName,
|
||||
email: profile.emails[0].value
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: Twitter doesn't expose emails by default.
|
||||
* If we don't get one NextAuth will create a placeholder in the form
|
||||
* `{provider}-{account-id}@localhost.localdomain`
|
||||
*
|
||||
* To have your Twitter oAuth return emails go to apps.twitter.com and add
|
||||
* links to your Terms and Conditions and Privacy Policy under the "Settings"
|
||||
* tab, then check the "Request email addresses" from users box under the
|
||||
* "Permissions" tab.
|
||||
**/
|
||||
if (process.env.TWITTER_KEY && process.env.TWITTER_SECRET) {
|
||||
providers.push({
|
||||
providerName: 'Twitter',
|
||||
providerOptions: {
|
||||
scope: []
|
||||
},
|
||||
Strategy: require('passport-twitter').Strategy,
|
||||
strategyOptions: {
|
||||
consumerKey: process.env.TWITTER_KEY,
|
||||
consumerSecret: process.env.TWITTER_SECRET,
|
||||
userProfileURL: 'https://api.twitter.com/1.1/account/verify_credentials.json?include_email=true'
|
||||
},
|
||||
getProfile(profile) {
|
||||
// Normalize profile into one with {id, name, email} keys
|
||||
return {
|
||||
id: profile.id,
|
||||
name: profile.displayName,
|
||||
email: (profile.emails && profile.emails[0].value) ? profile.emails[0].value : ''
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// next.config.js
|
||||
const fetch = require('isomorphic-unfetch')
|
||||
const withPlugins = require('next-compose-plugins');
|
||||
|
||||
const withOffline = require('next-offline')
|
||||
|
||||
const optimizedImages = require('next-optimized-images')
|
||||
|
||||
const { ANALYZE, DEV } = process.env
|
||||
|
||||
module.exports = withPlugins(
|
||||
[
|
||||
[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
|
||||
}
|
||||
}}
|
||||
],
|
||||
importScripts:
|
||||
['/static/js/firebase-messaging-sw.js',
|
||||
'/static/js/backgroundSync-sw.js'
|
||||
]
|
||||
},
|
||||
|
||||
}],
|
||||
[{
|
||||
// serverRuntimeConfig: { // Will only be available on the server side
|
||||
// // mySecret: 'secret',
|
||||
// // secondSecret: process.env.SECOND_SECRET // Pass through env variables
|
||||
// },
|
||||
publicRuntimeConfig: { // Will be available on both server and client
|
||||
staticFolder: '/static',
|
||||
push: {
|
||||
'defaultAction': 'https://application.bzh/' ,
|
||||
'defaultIconLink': 'https://application.bzh/static/images/icons/e-declicIco152x152.png' ,
|
||||
'defaultImageLink': 'https://application.bzh/static/images/banner/atc-site-web-responsive.jpg',
|
||||
'topics':[
|
||||
{ key: 'e-declic', value: false },
|
||||
{ key: 'PWA', value: false }
|
||||
]
|
||||
},
|
||||
wordpressUrl:{
|
||||
'menu':'https://api.e-declic.net/wp-json/menus/v1/menus/top',
|
||||
'posts':'https://api.e-declic.net/wp-json/wp/v2/posts',
|
||||
'postDetail':'https://api.e-declic.net/wp-json/wp/v2/posts',
|
||||
'pageDetail':'https://api.e-declic.net/wp-json/wp/v2/pages',
|
||||
'categories':'https://api.e-declic.net/wp-json/wp/v2/categories',
|
||||
'media':'https://api.e-declic.net/wp-json/wp/v2/media'
|
||||
|
||||
},
|
||||
ReCAPTCHA:{
|
||||
"siteKey":"6LcCvHcUAAAAAEw2gBZkoJcVvsNp1IgU4MnrIaRI"
|
||||
},
|
||||
eDeclicUrl:'http://www.e-declic.com',
|
||||
}}]
|
||||
|
||||
],
|
||||
{
|
||||
webpack: (config, { isServer }) => {
|
||||
if (ANALYZE) {
|
||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
|
||||
|
||||
config.plugins.push(new BundleAnalyzerPlugin({
|
||||
analyzerMode: 'server',
|
||||
analyzerPort: isServer ? 8888 : 8889,
|
||||
openAnalyzer: true
|
||||
}))
|
||||
}
|
||||
|
||||
config.module.rules.push(
|
||||
{
|
||||
test: /\.md$/,
|
||||
use: 'raw-loader'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
return config
|
||||
}
|
||||
},
|
||||
{
|
||||
generateBuildId: async () => {
|
||||
// For example get the latest git commit hash here
|
||||
|
||||
return 'v0.1.0'
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
||||
);
|
||||
@@ -0,0 +1,76 @@
|
||||
{
|
||||
"name": "WP-next",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@material-ui/core": "^3.2.0",
|
||||
"@material-ui/icons": "^3.0.1",
|
||||
"@material-ui/styles": "^3.0.0-alpha.8",
|
||||
"@material/animation": "^0.40.1",
|
||||
"@vx/gradient": "^0.0.165",
|
||||
"@vx/text": "^0.0.179",
|
||||
"axios": "^0.18.0",
|
||||
"body-parser": "^1.18.3",
|
||||
"connect-mongo": "^2.0.3",
|
||||
"cross-env": "^5.2.0",
|
||||
"cypress": "^3.1.0",
|
||||
"data-prefetch-link": "^1.1.3",
|
||||
"dotenv": "^6.1.0",
|
||||
"enzyme": "^3.8.0",
|
||||
"express": "^4.16.4",
|
||||
"firebase": "^5.7.0",
|
||||
"flubber": "^0.4.2",
|
||||
"fs": "^0.0.1-security",
|
||||
"googleapis": "^35.0.0",
|
||||
"he": "^1.2.0",
|
||||
"isomorphic-unfetch": "^3.0.0",
|
||||
"jss": "^9.8.7",
|
||||
"jss-nested": "^6.0.1",
|
||||
"lodash": "^4.17.11",
|
||||
"lscache": "^1.3.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mongodb": "^3.1.10",
|
||||
"nedb": "^1.8.0",
|
||||
"next": "^7.0.2",
|
||||
"next-auth": "^1.12.1",
|
||||
"next-compose-plugins": "^2.1.1",
|
||||
"next-offline": "^3.2.2",
|
||||
"next-optimized-images": "^1.4.1",
|
||||
"nodemailer": "4.1.0",
|
||||
"nodemailer-direct-transport": "^3.3.2",
|
||||
"nodemailer-smtp-transport": "^2.7.4",
|
||||
"now": "^11.4.6",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-linkedin-oauth2": "^1.5.0",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.8.0-alpha.1",
|
||||
"react-async-component": "^2.0.0",
|
||||
"react-cookies": "^0.1.0",
|
||||
"react-dom": "^16.8.0-alpha.1",
|
||||
"react-ga": "^2.5.6",
|
||||
"react-google-recaptcha": "^1.0.4",
|
||||
"react-jss": "^8.6.1",
|
||||
"react-markdown": "^4.0.3",
|
||||
"react-measure": "^2.2.2",
|
||||
"react-spring": "^6.1.7",
|
||||
"reactstrap": "^7.0.2",
|
||||
"start-server-and-test": "^1.7.5",
|
||||
"universal-cookie": "^3.0.7"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "cross-env PORT=3000 DEV=1 node server.js",
|
||||
"bs": "yarn build && yarn start",
|
||||
"pm2-prod": "yarn build && pm2 start --name 'next' -- start",
|
||||
"build": "next build && yarn copysw",
|
||||
"start": "cross-env PORT=8000 NODE_ENV=production node server.js",
|
||||
"test:cypress": "start-server-and-test http://localhost:8000 cypress",
|
||||
"cypress": "cypress run",
|
||||
"analyze": "cross-env ANALYZE=1 next build",
|
||||
"copysw": "cp -R static/js .next/static/"
|
||||
},
|
||||
"devDependencies": {
|
||||
"webpack-bundle-analyzer": "^3.0.3"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import React from 'react';
|
||||
import App, { Container } from 'next/app';
|
||||
import { MuiThemeProvider } from '@material-ui/core/styles';
|
||||
import CssBaseline from '@material-ui/core/CssBaseline';
|
||||
import JssProvider from 'react-jss/lib/JssProvider';
|
||||
import getPageContext from '../src/getPageContext';
|
||||
|
||||
import Router from 'next/router'
|
||||
import getConfig from 'next/config'
|
||||
import Head from 'next/head'
|
||||
|
||||
import { initGA, logPageView } from '../utils/analytics'
|
||||
|
||||
|
||||
const { publicRuntimeConfig } = getConfig()
|
||||
class MyApp extends App {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.pageContext = getPageContext();
|
||||
}
|
||||
|
||||
pageContext = null;
|
||||
|
||||
static async getInitialProps({ Component, ctx }) {
|
||||
let err = false;
|
||||
const res = await fetch(publicRuntimeConfig.wordpressUrl.menu)
|
||||
const menu = await res.json()
|
||||
|
||||
|
||||
if (menu.data && menu.data.status != 200)
|
||||
err = true;
|
||||
|
||||
let pageProps = {}
|
||||
|
||||
if (Component.getInitialProps) {
|
||||
pageProps = await Component.getInitialProps(ctx)
|
||||
}
|
||||
|
||||
return { pageProps: { ...pageProps }, menu, err }
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// Remove the server-side injected CSS.
|
||||
const jssStyles = document.querySelector('#jss-server-side');
|
||||
if (jssStyles && jssStyles.parentNode) {
|
||||
jssStyles.parentNode.removeChild(jssStyles);
|
||||
}
|
||||
if (process.env.DEV !== 1) {
|
||||
initGA()
|
||||
logPageView()
|
||||
Router.router.events.on('routeChangeComplete', logPageView)
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (process.env.DEV !== 1) {
|
||||
Router.router.events.off('routeChangeComplete', logPageView);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { Component, pageProps, menu } = this.props;
|
||||
|
||||
return (
|
||||
<Container>
|
||||
<Head>
|
||||
<title>e-declic</title>
|
||||
</Head>
|
||||
{/* Wrap every page in Jss and Theme providers */}
|
||||
<JssProvider
|
||||
registry={this.pageContext.sheetsRegistry}
|
||||
generateClassName={this.pageContext.generateClassName}
|
||||
>
|
||||
{/* MuiThemeProvider makes the theme available down the React
|
||||
tree thanks to React context. */}
|
||||
<MuiThemeProvider
|
||||
theme={this.pageContext.theme}
|
||||
sheetsManager={this.pageContext.sheetsManager}
|
||||
>
|
||||
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
|
||||
<CssBaseline />
|
||||
{/* Pass pageContext to the _document though the renderPage enhancer
|
||||
to render collected styles on server side.
|
||||
messagingSingleton={messagingSingleton} */}
|
||||
<Component pageContext={this.pageContext} menu={menu} {...pageProps} />
|
||||
</MuiThemeProvider>
|
||||
</JssProvider>
|
||||
<noscript>This is a modern pwa site. You must allow javascript !</noscript>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MyApp;
|
||||
@@ -0,0 +1,122 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Document, { Head, Main, NextScript } from 'next/document';
|
||||
import flush from 'styled-jsx/server';
|
||||
|
||||
class MyDocument extends Document {
|
||||
render() {
|
||||
const { pageContext } = this.props;
|
||||
|
||||
return (
|
||||
<html lang="fr" dir="ltr">
|
||||
<Head>
|
||||
<meta charSet="utf-8" />
|
||||
{/* TODO: A enlever avant MEP
|
||||
<meta name="robots" content="noindex, nofollow"></meta>
|
||||
*/}
|
||||
<meta name="google-site-verification" content="aR4mfPRusAD1JYt9Fu59tm-XWl3IAwHI-UKlwSTBdv8" />
|
||||
{/* Use minimum-scale=1 to enable GPU rasterization */}
|
||||
<meta name="viewport"
|
||||
content="minimum-scale=1, initial-scale=1, width=device-width, shrink-to-fit=no"
|
||||
/>
|
||||
{/* PWA primary color */}
|
||||
<meta name="theme-color" content={pageContext.theme.palette.primary.main} />
|
||||
{/* <link
|
||||
rel="stylesheet"
|
||||
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500"
|
||||
/> */}
|
||||
<link rel="manifest" href="/static/manifest.json" />
|
||||
<link rel='shortcut icon' type='image/x-icon' href='/static/favicon.ico' />
|
||||
|
||||
<script type="text/javascript" src="/static/js/pwaInstaller.js" ></script>
|
||||
|
||||
<link rel="mask-icon" href="/static/images/icons/e-declicIco.svg" color="#5bbad5" />
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/static/images/icons/e-declicIco32x32.png" />
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/images/icons/e-declicIco16x16.png" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<link rel="apple-touch-icon" href="/static/images/icons/e-declicIco57x57.png" />
|
||||
<link rel="apple-touch-icon" sizes="152x152" href="/static/images/icons/e-declicIco152x152.png" />
|
||||
<link rel="apple-touch-icon" sizes="167x167" href="/static/images/icons/e-declicIco167x167.png"/>
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/static/images/icons/e-declicIco180x180.png" />
|
||||
|
||||
<link href="/static/images/splashscreen/2048x2732.png" sizes="2048x2732" rel="apple-touch-startup-image" />
|
||||
<link href="/static/images/splashscreen/1668x2224.png" sizes="1668x2224" rel="apple-touch-startup-image" />
|
||||
<link href="/static/images/splashscreen/1536x2048.png" sizes="1536x2048" rel="apple-touch-startup-image" />
|
||||
<link href="/static/images/splashscreen/1125x2436.png" sizes="1125x2436" rel="apple-touch-startup-image" />
|
||||
<link href="/static/images/splashscreen/1242x2208.png" sizes="1242x2208" rel="apple-touch-startup-image" />
|
||||
<link href="/static/images/splashscreen/750x1334.png" sizes="750x1334" rel="apple-touch-startup-image" />
|
||||
<link href="/static/images/splashscreen/640x1136.png" sizes="640x1136" rel="apple-touch-startup-image" />
|
||||
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
|
||||
{/* <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"/> */}
|
||||
</Head>
|
||||
<body>
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MyDocument.getInitialProps = ctx => {
|
||||
|
||||
|
||||
|
||||
// Resolution order
|
||||
//
|
||||
// On the server:
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. document.getInitialProps
|
||||
// 4. app.render
|
||||
// 5. page.render
|
||||
// 6. document.render
|
||||
//
|
||||
// On the server with error:
|
||||
// 1. document.getInitialProps
|
||||
// 2. app.render
|
||||
// 3. page.render
|
||||
// 4. document.render
|
||||
//
|
||||
// On the client
|
||||
// 1. app.getInitialProps
|
||||
// 2. page.getInitialProps
|
||||
// 3. app.render
|
||||
// 4. page.render
|
||||
|
||||
// Render app and page and get the context of the page with collected side effects.
|
||||
let pageContext;
|
||||
const page = ctx.renderPage(Component => {
|
||||
const WrappedComponent = props => {
|
||||
pageContext = props.pageContext;
|
||||
return <Component {...props} />;
|
||||
};
|
||||
|
||||
WrappedComponent.propTypes = {
|
||||
pageContext: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
return WrappedComponent;
|
||||
});
|
||||
|
||||
return {
|
||||
...page,
|
||||
pageContext,
|
||||
// Styles fragment is rendered after the app and page rendering finish.
|
||||
styles: (
|
||||
<React.Fragment>
|
||||
<style
|
||||
id="jss-server-side"
|
||||
// eslint-disable-next-line react/no-danger
|
||||
dangerouslySetInnerHTML={{ __html: pageContext.sheetsRegistry.toString() }}
|
||||
/>
|
||||
{flush() || null}
|
||||
</React.Fragment>
|
||||
),
|
||||
};
|
||||
};
|
||||
|
||||
export default MyDocument;
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Creating a page named _error.js lets you override HTTP error messages
|
||||
*/
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { withRouter } from 'next/router'
|
||||
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
},
|
||||
});
|
||||
class ErrorPage extends React.Component {
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
errorCode: React.PropTypes.number.isRequired,
|
||||
url: React.PropTypes.string.isRequired
|
||||
}
|
||||
}
|
||||
|
||||
static getInitialProps({res, xhr}) {
|
||||
const errorCode = res ? res.statusCode : (xhr ? xhr.status : null)
|
||||
return {errorCode}
|
||||
}
|
||||
|
||||
render() {
|
||||
var response
|
||||
switch (this.props.errorCode) {
|
||||
case 200: // Also display a 404 if someone requests /_error explicitly
|
||||
case 404:
|
||||
response = (
|
||||
<div>
|
||||
<div>
|
||||
<h1 >Page Not Found</h1>
|
||||
<p>La page <strong>{ this.props.url }</strong> n'existe pas.</p>
|
||||
<p><Link href="/"><a>Retour au site</a></Link></p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
break
|
||||
case 500:
|
||||
response = (
|
||||
<div>
|
||||
<div >
|
||||
<h1>Internal Server Error</h1>
|
||||
<p>An internal server error occurred.</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
break
|
||||
default:
|
||||
response = (
|
||||
<div>
|
||||
<div >
|
||||
<h1 >HTTP { this.props.errorCode } Error</h1>
|
||||
<p>
|
||||
An <strong>HTTP { this.props.errorCode }</strong> error occurred while
|
||||
trying to access <strong>{ this.props.router.pathname }</strong>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default withRouter(withStyles(styles)(ErrorPage))
|
||||
@@ -0,0 +1,269 @@
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import fetch from 'isomorphic-fetch'
|
||||
import { Row, Col, Form, FormGroup, Label, Input, Button } from 'reactstrap'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
import Layout from '../components/MyLayout'
|
||||
import Cookies from 'universal-cookie'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
state={
|
||||
session: {},
|
||||
isSignedIn: false,
|
||||
name: '',
|
||||
email: '',
|
||||
emailVerified: false,
|
||||
alertText: null,
|
||||
alertStyle: null
|
||||
}
|
||||
|
||||
static async getInitialProps({req}) {
|
||||
let props = {};
|
||||
props.session = await NextAuth.init({req})
|
||||
props.linkedAccounts = await NextAuth.linked({req})
|
||||
return props
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
session: props.session,
|
||||
isSignedIn: (props.session.user) ? true : false,
|
||||
name: '',
|
||||
email: '',
|
||||
emailVerified: false,
|
||||
alertText: null,
|
||||
alertStyle: null
|
||||
}
|
||||
if (props.session.user) {
|
||||
this.state.name = props.session.user.name
|
||||
this.state.email = props.session.user.email
|
||||
}
|
||||
this.handleChange = this.handleChange.bind(this)
|
||||
this.onSubmit = this.onSubmit.bind(this)
|
||||
this.handleSignoutSubmit = this.handleSignoutSubmit.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const session = await NextAuth.init({force: true})
|
||||
this.setState({
|
||||
session: session,
|
||||
isSignedIn: (session.user) ? true : false
|
||||
})
|
||||
|
||||
// If the user bounces off to link/unlink their account we want them to
|
||||
// land back here after signing in with the other service / unlinking.
|
||||
const cookies = new Cookies()
|
||||
cookies.set('redirect_url', window.location.pathname, { path: '/' })
|
||||
|
||||
this.getProfile()
|
||||
}
|
||||
|
||||
getProfile() {
|
||||
fetch('/account/user', {
|
||||
credentials: 'include'
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(user => {
|
||||
if (!user.name || !user.email) return
|
||||
this.setState({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
emailVerified: user.emailVerified
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
handleChange(event) {
|
||||
this.setState({
|
||||
[event.target.name]: event.target.value
|
||||
})
|
||||
}
|
||||
|
||||
async onSubmit(e) {
|
||||
// Submits the URL encoded form without causing a page reload
|
||||
e.preventDefault()
|
||||
|
||||
this.setState({
|
||||
alertText: null,
|
||||
alertStyle: null
|
||||
})
|
||||
|
||||
const formData = {
|
||||
_csrf: await NextAuth.csrfToken(),
|
||||
//_csrf:this.state.session.csrfToken,
|
||||
name: this.state.name || '',
|
||||
email: this.state.email || ''
|
||||
}
|
||||
|
||||
// URL encode form
|
||||
// Note: This uses a x-www-form-urlencoded rather than sending JSON so that
|
||||
// the form also in browsers without JavaScript
|
||||
const encodedForm = Object.keys(formData).map((key) => {
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(formData[key])
|
||||
}).join('&')
|
||||
|
||||
fetch('/account/user', {
|
||||
credentials: 'include',
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: encodedForm
|
||||
})
|
||||
.then(async res => {
|
||||
if (res.status === 200) {
|
||||
this.getProfile()
|
||||
this.setState({
|
||||
alertText: 'Changes to your profile have been saved',
|
||||
alertStyle: 'alert-success',
|
||||
})
|
||||
// Force update session so that changes to name or email are reflected
|
||||
// immediately in the navbar (as we pass our session to it).
|
||||
this.setState({
|
||||
session: await NextAuth.init({force: true}), // Update session data
|
||||
})
|
||||
} else {
|
||||
this.setState({
|
||||
session: await NextAuth.init({force: true}), // Update session data
|
||||
alertText: 'Failed to save changes to your profile',
|
||||
alertStyle: 'alert-danger',
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async handleSignoutSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
// Save current URL so user is redirected back here after signing out
|
||||
const cookies = new Cookies()
|
||||
cookies.set('redirect_url', window.location.pathname, { path: '/' })
|
||||
|
||||
await NextAuth.signout()
|
||||
Router.push('/')
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.isSignedIn === true) {
|
||||
const alert = (this.state.alertText === null) ? <div/> : <div className={`alert ${this.state.alertStyle}`} role="alert">{this.state.alertText}</div>
|
||||
|
||||
return (
|
||||
<Layout menu={this.props.menu}>
|
||||
<Row className="mb-1">
|
||||
<Col xs="12">
|
||||
<h1 className="display-2">Your Account</h1>
|
||||
<p className="lead text-muted">
|
||||
Edit your profile and link accounts
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
{alert}
|
||||
<Row className="mt-4">
|
||||
<Col xs="12" md="8" lg="9">
|
||||
<Form method="post" action="/account/user" onSubmit={this.onSubmit}>
|
||||
<Input name="_csrf" type="hidden" value={this.state.session.csrfToken} onChange={()=>{}}/>
|
||||
<FormGroup row>
|
||||
<Label sm={2}>Name:</Label>
|
||||
<Col sm={10} md={8}>
|
||||
<Input name="name" value={this.state.name} onChange={this.handleChange}/>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
<FormGroup row>
|
||||
<Label sm={2}>Email:</Label>
|
||||
<Col sm={10} md={8}>
|
||||
<Input name="email" value={(this.state.email.match(/.*@localhost\.localdomain$/)) ? '' : this.state.email} onChange={this.handleChange}/>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
<FormGroup row>
|
||||
<Col sm={12} md={10}>
|
||||
<p className="text-right">
|
||||
<Button color="primary" type="submit">Save Changes</Button>
|
||||
</p>
|
||||
</Col>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
<Form id="signout" method="post" action="/auth/signout" onSubmit={this.handleSignoutSubmit}>
|
||||
<input name="_csrf" type="hidden" value={this.props.session.csrfToken}/>
|
||||
<Button type="submit" block className="pl-4 rounded-0 text-left dropdown-item"><span className="icon ion-md-log-out mr-1"></span> Sign out</Button>
|
||||
</Form>
|
||||
</Col>
|
||||
<Col xs="12" md="4" lg="3">
|
||||
<LinkAccounts
|
||||
session={this.props.session}
|
||||
linkedAccounts={this.props.linkedAccounts}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row>
|
||||
<Col>
|
||||
<h2>Delete your account</h2>
|
||||
<p>
|
||||
If you delete your account it will be erased immediately.
|
||||
You can sign up again at any time.
|
||||
</p>
|
||||
<Form id="signout" method="post" action="/account/delete">
|
||||
<input name="_csrf" type="hidden" value={this.state.session.csrfToken}/>
|
||||
<Button type="submit" color="outline-danger"><span className="icon ion-md-trash mr-1"></span> Delete Account</Button>
|
||||
</Form>
|
||||
</Col>
|
||||
</Row>
|
||||
</Layout>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Layout {...this.props} navmenu={false}>
|
||||
<Row>
|
||||
<Col xs="12" className="text-center pt-5 pb-5">
|
||||
<p className="lead m-0">
|
||||
<Link href="/auth"><a>Sign in to manage your profile</a></Link>
|
||||
</p>
|
||||
</Col>
|
||||
</Row>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkAccounts extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{
|
||||
Object.keys(this.props.linkedAccounts).map((provider, i) => {
|
||||
return <LinkAccount key={i} provider={provider} session={this.props.session} linked={this.props.linkedAccounts[provider]}/>
|
||||
})
|
||||
}
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class LinkAccount extends React.Component {
|
||||
render() {
|
||||
if (this.props.linked === true) {
|
||||
return (
|
||||
<form method="post" action={`/auth/oauth/${this.props.provider.toLowerCase()}/unlink`}>
|
||||
<input name="_csrf" type="hidden" value={this.props.session.csrfToken}/>
|
||||
<p>
|
||||
<button className="btn btn-block btn-outline-danger" type="submit">
|
||||
Unlink from {this.props.provider}
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<p>
|
||||
<a className="btn btn-block btn-outline-primary" href={`/auth/oauth/${this.props.provider.toLowerCase()}`}>
|
||||
Link with {this.props.provider}
|
||||
</a>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import Router from 'next/router'
|
||||
import Cookies from 'universal-cookie'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
static async getInitialProps({req}) {
|
||||
const session = await NextAuth.init({force: true, req: req})
|
||||
|
||||
const cookies = new Cookies((req && req.headers.cookie) ? req.headers.cookie : null)
|
||||
|
||||
// If the user is signed in, we look for a redirect URL cookie and send
|
||||
// them to that page, so that people signing in end up back on the page they
|
||||
// were on before signing in. Defaults to '/'.
|
||||
let redirectTo = '/'
|
||||
if (session.user) {
|
||||
// Read redirect URL to redirect to from cookies
|
||||
redirectTo = cookies.get('redirect_url') || redirectTo
|
||||
|
||||
// Allow relative paths only - strip protocol/host/port if they exist.
|
||||
redirectTo = redirectTo.replace( /^[a-zA-Z]{3,5}\:\/{2}[a-zA-Z0-9_.:-]+\//, '')
|
||||
}
|
||||
|
||||
return {
|
||||
session: session,
|
||||
redirectTo: redirectTo
|
||||
}
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
// Get latest session data after rendering on client *then* redirect.
|
||||
// The ensures client state is always updated after signing in or out.
|
||||
// (That's why we use a callback page)
|
||||
const session = await NextAuth.init({force: true})
|
||||
Router.push(this.props.redirectTo || '/')
|
||||
}
|
||||
|
||||
render() {
|
||||
// Provide a link for clients without JavaScript as a fallback.
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Head>
|
||||
<meta charSet="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||||
<script src="https://cdn.polyfill.io/v2/polyfill.min.js"/>
|
||||
</Head>
|
||||
<a href={this.props.redirectTo}>
|
||||
|
||||
</a>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
|
||||
import Layout from '../../components/MyLayout'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
static async getInitialProps({req, res, query}) {
|
||||
let props = {};
|
||||
props.session = await NextAuth.init({force: true, req: req})
|
||||
|
||||
// If signed in already, instead of displaying message send to callback page
|
||||
// which should redirect them to whatever page it normally sends clients to
|
||||
if (props.session.user) {
|
||||
if (req) {
|
||||
res.redirect('/auth/callback')
|
||||
} else {
|
||||
Router.push('/auth/callback')
|
||||
}
|
||||
}
|
||||
|
||||
props.email = query.email
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Layout menu={this.props.menu}>
|
||||
<div className="text-center pt-5 pb-5">
|
||||
<h1 className="display-4">Check your email</h1>
|
||||
<p className="lead">
|
||||
A sign in link has been sent to { (this.props.email) ? <span className="font-weight-bold">{this.props.email}</span> : <span>your inbox</span> }.
|
||||
</p>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
static async getInitialProps({req}) {
|
||||
return {
|
||||
session: await NextAuth.init({req}),
|
||||
linkedAccounts: await NextAuth.linked({req}),
|
||||
providers: await NextAuth.providers({req})
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
email: '',
|
||||
password: '',
|
||||
session: this.props.session
|
||||
}
|
||||
this.handleEmailChange = this.handleEmailChange.bind(this)
|
||||
this.handlePasswordChange = this.handlePasswordChange.bind(this)
|
||||
this.handleSignInSubmit = this.handleSignInSubmit.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
if (this.props.session.user) {
|
||||
Router.push(`/auth/`)
|
||||
}
|
||||
}
|
||||
|
||||
handleEmailChange(event) {
|
||||
this.setState({
|
||||
email: event.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handlePasswordChange(event) {
|
||||
this.setState({
|
||||
password: event.target.value
|
||||
})
|
||||
}
|
||||
|
||||
handleSignInSubmit(event) {
|
||||
event.preventDefault()
|
||||
|
||||
// An object passed NextAuth.signin will be passed to your signin() function
|
||||
NextAuth.signin({
|
||||
email: this.state.email,
|
||||
password: this.state.password
|
||||
})
|
||||
.then(authenticated => {
|
||||
Router.push(`/auth/callback`)
|
||||
})
|
||||
.catch(() => {
|
||||
alert("Authentication failed.")
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.session.user) {
|
||||
return null
|
||||
} else {
|
||||
return (
|
||||
<div className="container">
|
||||
<div className="text-center">
|
||||
<h1 className="display-5 mt-4 mb-2">PWA - Custom Sign In</h1>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm-12 col-md-10 col-lg-8 col-xl-7 mr-auto ml-auto">
|
||||
|
||||
<div className="card mt-3 mb-3">
|
||||
<h4 className="card-header">Sign In</h4>
|
||||
<div className="card-body pb-0">
|
||||
<form id="signin" method="post" action="/auth/signin" onSubmit={this.handleSignInSubmit}>
|
||||
<input name="_csrf" type="hidden" value={this.state.session.csrfToken}/>
|
||||
<p>
|
||||
<label htmlFor="email">Email address</label><br/>
|
||||
<input name="email" type="text" placeholder="j.smith@example.com" id="email" className="form-control" value={this.state.email} onChange={this.handleEmailChange}/>
|
||||
</p>
|
||||
<p>
|
||||
<label htmlFor="password">Password</label><br/>
|
||||
<input name="password" type="password" placeholder="" id="password" className="form-control" value={this.state.password} onChange={this.handlePasswordChange}/>
|
||||
</p>
|
||||
<p className="text-right">
|
||||
<button id="submitButton" type="submit" className="btn btn-outline-primary">Sign in</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-center">
|
||||
<Link href="/auth"><a>Back</a></Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
|
||||
import Layout from '../../components/MyLayout'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
static async getInitialProps({req, query}) {
|
||||
let props = {};
|
||||
props.action = query.action || null
|
||||
props.type = query.type || null
|
||||
props.service = query.service || null
|
||||
return props
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.action == 'signin' && this.props.type == 'oauth') {
|
||||
return(
|
||||
<Layout menu={this.props.menu}>
|
||||
<div className="text-center mb-5">
|
||||
<h1 className="display-4 mt-5 mb-3">Unable to sign in</h1>
|
||||
<p className="lead">An account associated with your email address already exists.</p>
|
||||
<p className="lead"><Link href="/auth"><a>Sign in with email or another service</a></Link></p>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm-8 mr-auto ml-auto mb-5">
|
||||
<div className="text-muted">
|
||||
<h4 className="mb-2">Why am I seeing this?</h4>
|
||||
<p className="mb-2">
|
||||
It looks like you might have already signed up using another service.
|
||||
</p>
|
||||
<p className="mb-3">
|
||||
To protect your account, if you have perviously signed up
|
||||
using another service you must link accounts before you
|
||||
can use a different service to sign in.
|
||||
</p>
|
||||
<h4 className="mb-2">How do I fix this?</h4>
|
||||
<p className="mb-0">
|
||||
To sign in using another service, first sign in using your email address then link accounts.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
} else if (this.props.action == 'signin' && this.props.type == 'token-invalid') {
|
||||
return(
|
||||
<Layout menu={this.props.menu}>
|
||||
<div className="text-center mb-5">
|
||||
<h1 className="display-4 mt-5 mb-2">Link not valid</h1>
|
||||
<p className="lead">This sign in link is no longer valid.</p>
|
||||
<p className="lead"><Link href="/auth"><a>Get a new sign in link</a></Link></p>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
} else {
|
||||
return(
|
||||
<Layout menu={this.props.menu}>
|
||||
<div className="text-center mb-5">
|
||||
<h1 className="display-4 mt-5">Error signing in</h1>
|
||||
<p className="lead">An error occured while trying to sign in.</p>
|
||||
<p className="lead"><Link href="/auth"><a>Sign in with email or another service</a></Link></p>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import React from 'react'
|
||||
import Head from 'next/head'
|
||||
import Router from 'next/router'
|
||||
import Link from 'next/link'
|
||||
import { Row, Col } from 'reactstrap'
|
||||
import Cookies from 'universal-cookie'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
import Layout from '../../components/MyLayout'
|
||||
import SignIn from '../../components/signin'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
static async getInitialProps({req, res, query}) {
|
||||
let props = {};
|
||||
props.session = await NextAuth.init({force: true, req: req})
|
||||
props.providers = await NextAuth.providers({req})
|
||||
|
||||
// If signed in already, redirect to account management page.
|
||||
if (props.session.user) {
|
||||
if (req) {
|
||||
res.redirect('/account')
|
||||
} else {
|
||||
Router.push('/account')
|
||||
}
|
||||
}
|
||||
|
||||
// If passed a redirect parameter, save it as a cookie
|
||||
if (query.redirect) {
|
||||
const cookies = new Cookies((req && req.headers.cookie) ? req.headers.cookie : null)
|
||||
cookies.set('redirect_url', query.redirect, { path: '/' })
|
||||
}
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.props.session.user) {
|
||||
return (
|
||||
<Layout menu={this.props.menu}>
|
||||
<p className="lead text-center mt-5 mb-5">
|
||||
<Link href="/auth"><a>Manage your profile</a></Link>
|
||||
</p>
|
||||
</Layout>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Layout menu={this.props.menu}>
|
||||
<h1 className="text-center display-4 mt-5">Sign up / Sign in</h1>
|
||||
<Row className="mb-5">
|
||||
<Col lg="8" className="mr-auto ml-auto" style={{marginBottom: 20}}>
|
||||
<SignIn session={this.props.session} providers={this.props.providers}/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,295 @@
|
||||
import Layout from '../components/MyLayout.js'
|
||||
import axios from 'axios'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Slide from '@material-ui/core/Slide';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import TextField from '@material-ui/core/TextField';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import ReCAPTCHA from "react-google-recaptcha";
|
||||
import green from '@material-ui/core/colors/green';
|
||||
import orange from '@material-ui/core/colors/orange';
|
||||
import SnackbarContent from '@material-ui/core/SnackbarContent';
|
||||
import { Transition, config } from 'react-spring'
|
||||
import getConfig from 'next/config'
|
||||
import Head from 'next/head'
|
||||
|
||||
const {publicRuntimeConfig} = getConfig()
|
||||
|
||||
//variable for client side only import
|
||||
let back = ''
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
margin: theme.spacing.unit,
|
||||
maxWidth: '400px', [theme.breakpoints.down('md')]: {
|
||||
maxWidth: `${100 - (theme.spacing.unit)}vw`
|
||||
},
|
||||
|
||||
},
|
||||
wrapper: {
|
||||
maxWidth: '400px',
|
||||
[theme.breakpoints.down('md')]: {
|
||||
maxWidth: `${100 - (theme.spacing.unit)}vw`
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
'& form':{
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: `${100 - (theme.spacing.unit)}vw`
|
||||
},
|
||||
padding: theme.spacing.unit * 2,
|
||||
|
||||
display:'flex',
|
||||
flexWrap: 'wrap',
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
website: {
|
||||
width: 400,
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: `${100 - (theme.spacing.unit)}vw`
|
||||
},
|
||||
},
|
||||
textField: {
|
||||
|
||||
width: '100%'
|
||||
},
|
||||
button: {
|
||||
|
||||
margin: theme.spacing.unit,
|
||||
alignSelf:'flex-end'
|
||||
},
|
||||
link: {
|
||||
|
||||
cursor: 'pointer'
|
||||
},
|
||||
success: {
|
||||
backgroundColor: green[600],
|
||||
},
|
||||
error: {
|
||||
backgroundColor: theme.palette.error.dark,
|
||||
},
|
||||
warning: {
|
||||
backgroundColor: orange[600],
|
||||
},
|
||||
|
||||
spock: {
|
||||
display: 'inline'
|
||||
},
|
||||
errorCaptcha: {
|
||||
border: '2px',
|
||||
borderColor: 'red',
|
||||
borderStyle: 'solid'
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
let spring = { ...config.default, precision: 0.1 }
|
||||
const recaptchaRef = React.createRef();
|
||||
class Contact extends React.Component {
|
||||
state = {
|
||||
nom:"",
|
||||
prenom:"",
|
||||
mail:"",
|
||||
tel:"",
|
||||
message: "",
|
||||
checked: false,
|
||||
openSnack: false,
|
||||
loading: false,
|
||||
error: false,
|
||||
warning: false,
|
||||
success: false,
|
||||
snackMessage: ""
|
||||
};
|
||||
|
||||
handleMessage = event => {
|
||||
this.setState({ success: false, message: event.target.value })
|
||||
}
|
||||
handleField = e => {
|
||||
this.setState({ success: false, [e.target.name]: e.target.value })
|
||||
}
|
||||
sendMessage = (recaptchaValue) => {
|
||||
|
||||
axios.post("/feedback", {
|
||||
nom:this.state.nom,
|
||||
prenom:this.state.prenom,
|
||||
tel:this.state.tel,
|
||||
mail:this.state.mail,
|
||||
message: this.state.message,
|
||||
recaptchaValue: recaptchaValue
|
||||
}).then(response => {
|
||||
this.setState({ success: true, error: false, warning: false, message: '', snackMessage: "Votre message à bien été envoyé.", loading: false, openSnack: true })
|
||||
}).catch(error => {
|
||||
if (error.status != 500) {
|
||||
if (process.browser) {
|
||||
// client-side-only code
|
||||
back.saveEventDataLocally([{ message: this.state.message, recaptchaValue }]);
|
||||
}
|
||||
this.setState({ success: false, error: false, warning: true,
|
||||
snackMessage: "📴 Votre message n'a pas pu aboutir, mais a été sauvegardé. Nous l'enverrons lorsque nous aurons une meilleure connectivité.",
|
||||
loading: false, openSnack: true })
|
||||
}
|
||||
else
|
||||
this.setState({ success: false, error: true, warning: false,
|
||||
snackMessage: "Une erreur système est survenue.",
|
||||
loading: false, openSnack: true })
|
||||
});
|
||||
}
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ openSnack: false });
|
||||
}
|
||||
|
||||
handleSubmit = e => {
|
||||
e.preventDefault();
|
||||
const recaptchaValue = recaptchaRef.current.getValue();
|
||||
if (this.state.mail === '' || this.state.message === '' || recaptchaValue === '')
|
||||
this.setState({ success: false, error: true, warning: false,
|
||||
snackMessage: "Veuillez entrer les champs necessaires et valider le Captcha.",
|
||||
loading: false, openSnack: true })
|
||||
else
|
||||
this.setState({ success: false, error: false, warning: false, loading: true }, this.sendMessage(recaptchaValue))
|
||||
}
|
||||
config = (item, state) => (state === 'leave' ? [{ duration: 1000 }, spring] : [{ duration: 5000 }, spring])
|
||||
|
||||
async componentDidMount() {
|
||||
//loading lib for client side only
|
||||
let result = await import("../src/backSync.js")
|
||||
back = result.default
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<Layout menu={this.props.menu}>
|
||||
<Head>
|
||||
<title>Devis Développement PWA</title>
|
||||
<meta name="description" content="Vous souhaitez créer une PWA, développer des fonctionnalités à votre site web et améliorer l'expérience utilisateur sur votre site internet? Contactez-nous pour parler de votre projet." />
|
||||
<link rel="canonical" href="https://www.application.bzh/contact"/>
|
||||
</Head>
|
||||
<div className={classes.root}>
|
||||
<div className={classes.wrapper}>
|
||||
<Slide direction="right" in={true} mountOnEnter unmountOnExit timeout={800}>
|
||||
<Paper elevation={4} className={classes.paper}>
|
||||
{this.state.loading && (
|
||||
<div className="alert alert-info">
|
||||
<CircularProgress color="secondary" />Validating your inputs...
|
||||
</div>
|
||||
)}
|
||||
{!this.state.loading &&
|
||||
(<form onSubmit={this.handleSubmit}>
|
||||
<TextField
|
||||
id="prenom"
|
||||
name="prenom"
|
||||
label="Prénom"
|
||||
value={this.state.prenom}
|
||||
onChange={this.handleField}
|
||||
className={classes.textField}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
id="nom"
|
||||
name="nom"
|
||||
label="Nom"
|
||||
value={this.state.nom}
|
||||
onChange={this.handleField}
|
||||
className={classes.textField}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
id="mail"
|
||||
name="mail"
|
||||
label="Email*"
|
||||
value={this.state.mail}
|
||||
onChange={this.handleField}
|
||||
className={classes.textField}
|
||||
margin="normal"
|
||||
error={this.state.error}
|
||||
/>
|
||||
<TextField
|
||||
id="tel"
|
||||
name="tel"
|
||||
label="Tel"
|
||||
value={this.state.tel}
|
||||
onChange={this.handleField}
|
||||
className={classes.textField}
|
||||
margin="normal"
|
||||
/>
|
||||
<TextField
|
||||
id="standard-multiline-flexible"
|
||||
label="Message*"
|
||||
multiline
|
||||
rowsMax={10}
|
||||
rows={4}
|
||||
value={this.state.message}
|
||||
onChange={this.handleMessage}
|
||||
className={classes.textField}
|
||||
margin="normal"
|
||||
error={this.state.error}
|
||||
/>
|
||||
<div className={this.state.error ? classes.errorCaptcha : ''}>
|
||||
<ReCAPTCHA
|
||||
id="component-outlined"
|
||||
ref={recaptchaRef}
|
||||
sitekey={publicRuntimeConfig.ReCAPTCHA.siteKey}
|
||||
size="normal"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<Button type="submit" variant="outlined" color="secondary" className={classes.button}>
|
||||
Send
|
||||
</Button>
|
||||
<Transition
|
||||
config={this.config}
|
||||
items={this.state.success}
|
||||
from={{ opacity: 0 }}
|
||||
enter={{ opacity: 1 }}
|
||||
leave={{ opacity: 0 }}>
|
||||
{show =>
|
||||
show && (props => <div style={props} className={classes.spock}>🖖</div>)
|
||||
}
|
||||
</Transition>
|
||||
</form>)
|
||||
}
|
||||
</Paper>
|
||||
</Slide>
|
||||
</div>
|
||||
<br></br>
|
||||
<div>
|
||||
<Paper elevation={4} className={classes.website}>
|
||||
<a href={publicRuntimeConfig.eDeclicUrl} >
|
||||
<Typography variant="subtitle1" color="secondary" className={classes.link} >
|
||||
www.e-declic.com
|
||||
</Typography>
|
||||
</a>
|
||||
</Paper>
|
||||
</div>
|
||||
</div>
|
||||
<Snackbar
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
open={this.state.openSnack}
|
||||
onClose={this.handleClose}
|
||||
autoHideDuration={6000}
|
||||
TransitionComponent={(props)=><Slide {...props} direction="left" />}
|
||||
ContentProps={{
|
||||
'aria-describedby': 'message-id',
|
||||
}}
|
||||
>
|
||||
<SnackbarContent
|
||||
aria-describedby="message-id"
|
||||
className={classes[this.state.error ? 'error' : this.state.warning ? 'warning' : 'success']}
|
||||
message={<span id="message-id">{this.state.snackMessage}</span>}
|
||||
/>
|
||||
</Snackbar>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Contact)
|
||||
@@ -0,0 +1,42 @@
|
||||
import React from 'react'
|
||||
import Layout from '../components/MyLayout.js'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import Head from 'next/head'
|
||||
import _ from 'lodash'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
margin: theme.spacing.unit,
|
||||
height: 250,
|
||||
width: 400
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
class Index extends React.Component {
|
||||
state = {
|
||||
};
|
||||
|
||||
|
||||
componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes, menu} = this.props;
|
||||
return (
|
||||
<Layout menu={menu}>
|
||||
<Head>
|
||||
<title>Boilerplate PWA</title>
|
||||
<meta name="description" content="Vous souhaitez créer une PWA, développer des fonctionnalités à votre site web et améliorer l'expérience utilisateur sur votre site internet? Contactez-nous pour parler de votre projet." />
|
||||
<link rel="canonical" href="https://pwa-boiler.bzh" />
|
||||
</Head>
|
||||
|
||||
<div className={classes.root}>Hello world !</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
export default withStyles(styles)(Index)
|
||||
@@ -0,0 +1,279 @@
|
||||
import React, { Component } from 'react'
|
||||
import { withStyles, getMuiTheme } from '@material-ui/core/styles';
|
||||
import Layout from '../components/MyLayout.js'
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import axios from 'axios'
|
||||
import FormLabel from '@material-ui/core/FormLabel';
|
||||
import FormControl from '@material-ui/core/FormControl';
|
||||
import FormGroup from '@material-ui/core/FormGroup';
|
||||
import FormControlLabel from '@material-ui/core/FormControlLabel';
|
||||
import Switch from '@material-ui/core/Switch';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import SnackbarContent from '@material-ui/core/SnackbarContent';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import getConfig from 'next/config'
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
|
||||
var EdeclicLib=null
|
||||
|
||||
const { publicRuntimeConfig } = getConfig()
|
||||
//variable for client side only import
|
||||
let back = ''
|
||||
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
margin: theme.spacing.unit,
|
||||
|
||||
},
|
||||
paper: {
|
||||
width: 400,
|
||||
[theme.breakpoints.down('md')]: {
|
||||
width: `${100 - (theme.spacing.unit)}vw`
|
||||
},
|
||||
padding: theme.spacing.unit * 2
|
||||
},
|
||||
colorSwitchBase: {
|
||||
color: theme.palette.primary,
|
||||
'&$colorChecked': {
|
||||
color: theme.palette.primary,
|
||||
'& + $colorBar': {
|
||||
backgroundColor: theme.palette.primary,
|
||||
},
|
||||
},
|
||||
},
|
||||
colorBar: {},
|
||||
colorChecked: {},
|
||||
bandeau: {
|
||||
maxHeight: '5vh',
|
||||
width: '100%',
|
||||
backgroundColor: theme.palette.secondary,
|
||||
margin: theme.spacing.unit * 2
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
class parametres extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.handleChange = this.handleChange.bind(this);
|
||||
|
||||
}
|
||||
state = {
|
||||
general: false,
|
||||
topics: publicRuntimeConfig.push.topics,
|
||||
loading: false,
|
||||
openSnack: false,
|
||||
error: false,
|
||||
warning: false,
|
||||
success: false,
|
||||
snackMessage: "",
|
||||
isSafari: false
|
||||
};
|
||||
|
||||
async componentDidMount() {
|
||||
|
||||
EdeclicLib = (await import('../components/firebase-client.js')).default
|
||||
if (!EdeclicLib.messaging)
|
||||
return
|
||||
if (EdeclicLib.isSupported() === false) {
|
||||
this.setState({ isSafari: true });
|
||||
return;
|
||||
}
|
||||
|
||||
EdeclicLib.askForToken().then((token) => {
|
||||
console.log("componentDidMount token received :");
|
||||
console.log(token);
|
||||
const url = "/push/info/" + token
|
||||
axios.get(url).then(response => {
|
||||
this.refreshCheckboxes(!!token, response.data.topics)
|
||||
}).catch(error => {
|
||||
console.log(error)
|
||||
if (error.status != 500) {
|
||||
if (process.browser) {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
})
|
||||
}).catch(error => {
|
||||
console.log("error out" + error)
|
||||
if (error.status != 500) {
|
||||
if (process.browser) {
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
refreshCheckboxes(hasToken, topics) {
|
||||
//general
|
||||
this.setState({ general: hasToken })
|
||||
|
||||
//all topics
|
||||
if (!!topics) {
|
||||
const topicsTemp = cloneDeep(this.state.topics)
|
||||
topicsTemp.map(temp =>
|
||||
temp.value = !!topics[temp.key]
|
||||
|
||||
)
|
||||
this.setState({ topics: topicsTemp })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async sendPushTopicSubscription(topic) {
|
||||
var token = await EdeclicLib.askForToken();
|
||||
|
||||
const url = "/push/subscribe"
|
||||
axios.post(url, {
|
||||
token, topic
|
||||
}).then(response => {
|
||||
this.setState({ success: true, error: false, warning: false, message: '', snackMessage: "Le topic a bien été ajouté.", loading: false, openSnack: true })
|
||||
}).catch(error => {
|
||||
if (error.status != 500) {
|
||||
if (process.browser) {
|
||||
// client-side-only code
|
||||
// back.saveEventDataLocally([{ message: this.state.message }]);
|
||||
}
|
||||
}
|
||||
this.setState({ success: false, error: false, warning: true, snackMessage: "📴 Votre souscription au topic n'a pas pu aboutir, mais a été sauvegardé. Nous l'enverrons lorsque nous aurons une meilleure connectivité.", loading: false, openSnack: true })
|
||||
});
|
||||
}
|
||||
async sendPushTopicUnSubscription(topic) {
|
||||
var token = await EdeclicLib.askForToken();
|
||||
|
||||
const url = "/push/unsubscribe"
|
||||
axios.post(url, {
|
||||
token, topic
|
||||
}).then(response => {
|
||||
this.setState({ success: true, error: false, warning: false, message: '', snackMessage: "Le topic a bien été retiré.", loading: false, openSnack: true })
|
||||
}).catch(error => {
|
||||
if (error.status != 500) {
|
||||
if (process.browser) {
|
||||
// client-side-only code
|
||||
//back.saveEventDataLocally([{ message: this.state.message }]);
|
||||
}
|
||||
}
|
||||
this.setState({ success: false, error: false, warning: true, snackMessage: "📴 Votre désengagement au topic n'a pas pu aboutir, mais a été notifié. Nous l'enverrons lorsque nous aurons une meilleure connectivité.", loading: false, openSnack: true })
|
||||
});
|
||||
}
|
||||
handleChange = name => event => {
|
||||
|
||||
if (name === 'general') {
|
||||
console.log('general')
|
||||
if (event.target.checked === true) {
|
||||
console.log('event.target.checked === true')
|
||||
|
||||
if (!process.browser) return
|
||||
EdeclicLib.askForToken();
|
||||
console.log('token asked')
|
||||
this.setState({ general: true })
|
||||
}
|
||||
else {
|
||||
console.log('')
|
||||
|
||||
if (!process.browser) return
|
||||
EdeclicLib.deleteToken();
|
||||
let topicsTemp = cloneDeep(this.state.topics)
|
||||
topicsTemp.map(a => a.value = false)
|
||||
this.setState({ topics: topicsTemp, general: false })
|
||||
console.log('')
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (event.target.checked === true) {
|
||||
this.sendPushTopicSubscription(name)
|
||||
}
|
||||
else {
|
||||
this.sendPushTopicUnSubscription(name)
|
||||
}
|
||||
|
||||
let topicsTemp = cloneDeep(this.state.topics)
|
||||
topicsTemp.map(temp => {
|
||||
if (temp.key === name)
|
||||
temp.value = event.target.checked
|
||||
})
|
||||
|
||||
this.setState({ topics: topicsTemp, general: true })
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ openSnack: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<Layout menu={this.props.menu}>
|
||||
<div className={classes.root}>
|
||||
<div className={classes.bandeau} hidden={!this.state.isSafari}>
|
||||
<Typography variant="h6">🚧 Votre navigateur ne permet pas la reception de notification. 🚧</Typography>
|
||||
</div>
|
||||
<Paper elevation={4} className={classes.paper}>
|
||||
<FormControl component="fieldset">
|
||||
<FormLabel component="legend">Je souhaites recevoir des notifications : </FormLabel>
|
||||
<FormGroup>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
disabled={this.state.isSafari}
|
||||
checked={this.state.general}
|
||||
onChange={this.handleChange('general')}
|
||||
value="general"
|
||||
classes={{
|
||||
switchBase: classes.colorSwitchBase,
|
||||
}}
|
||||
/>
|
||||
}
|
||||
label="Générales"
|
||||
/>
|
||||
|
||||
{this.state.topics.map(ar =>
|
||||
|
||||
<FormControlLabel key={ar.key}
|
||||
control={
|
||||
<Switch
|
||||
disabled={this.state.isSafari}
|
||||
checked={ar.value}
|
||||
onChange={this.handleChange(ar.key)}
|
||||
value={ar.key}
|
||||
/>
|
||||
}
|
||||
label={`Sur ${ar.key}`}
|
||||
/>, this)}
|
||||
</FormGroup>
|
||||
</FormControl>
|
||||
</Paper>
|
||||
</div>
|
||||
|
||||
<Snackbar
|
||||
autoHideDuration={4000}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
open={this.state.openSnack}
|
||||
onClose={this.handleClose}
|
||||
ContentProps={{
|
||||
'aria-describedby': 'message-id',
|
||||
}}
|
||||
>
|
||||
<SnackbarContent
|
||||
|
||||
aria-describedby="message-id"
|
||||
className={classes[this.state.error ? 'error' : this.state.warning ? 'warning' : 'success']}
|
||||
message={<span id="message-id">{this.state.snackMessage}</span>}
|
||||
/></Snackbar>
|
||||
</Layout >
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default withStyles(styles)(parametres)
|
||||
@@ -0,0 +1,362 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
import Layout from '../components/MyLayout.js'
|
||||
import withStyles from '@material-ui/core/styles/withStyles';
|
||||
import Paper from '@material-ui/core/Paper';
|
||||
import Stepper from '@material-ui/core/Stepper';
|
||||
import Step from '@material-ui/core/Step';
|
||||
import StepLabel from '@material-ui/core/StepLabel';
|
||||
import Button from '@material-ui/core/Button';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import PushParamForm from '../components/push/PushParamForm';
|
||||
import PushReview from '../components/push/PushReview';
|
||||
import PushTargetForm from '../components/push/PushTargetForm';
|
||||
import CircularProgress from '@material-ui/core/CircularProgress';
|
||||
import axios from 'axios'
|
||||
import getConfig from 'next/config'
|
||||
import green from '@material-ui/core/colors/green';
|
||||
import orange from '@material-ui/core/colors/orange';
|
||||
import Snackbar from '@material-ui/core/Snackbar';
|
||||
import SnackbarContent from '@material-ui/core/SnackbarContent';
|
||||
import dynamic from 'next/dynamic'
|
||||
import Link from 'next/link'
|
||||
import Cookies from 'universal-cookie'
|
||||
import Router from 'next/router'
|
||||
|
||||
const styles = theme => ({
|
||||
appBar: {
|
||||
position: 'relative',
|
||||
},
|
||||
layout: {
|
||||
width: 'auto',
|
||||
marginLeft: theme.spacing.unit * 2,
|
||||
marginRight: theme.spacing.unit * 2,
|
||||
[theme.breakpoints.up(600 + theme.spacing.unit * 2 * 2)]: {
|
||||
width: 600,
|
||||
marginLeft: 'auto',
|
||||
marginRight: 'auto',
|
||||
},
|
||||
},
|
||||
paper: {
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
marginBottom: theme.spacing.unit * 3,
|
||||
padding: theme.spacing.unit * 2,
|
||||
[theme.breakpoints.up(600 + theme.spacing.unit * 3 * 2)]: {
|
||||
marginTop: theme.spacing.unit * 6,
|
||||
marginBottom: theme.spacing.unit * 6,
|
||||
padding: theme.spacing.unit * 3,
|
||||
},
|
||||
},
|
||||
stepper: {
|
||||
padding: `${theme.spacing.unit * 3}px 0 ${theme.spacing.unit * 5}px`,
|
||||
},
|
||||
buttons: {
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
button: {
|
||||
marginTop: theme.spacing.unit * 3,
|
||||
marginLeft: theme.spacing.unit,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
const { publicRuntimeConfig } = getConfig()
|
||||
|
||||
let back = ''
|
||||
const steps = ['Parameters', 'Target', 'Review'];
|
||||
|
||||
class PushCreator extends React.Component {
|
||||
state = {
|
||||
session: this.props.session,
|
||||
isSignedIn: (this.props.session.user) ? true : false,
|
||||
activeStep: 0,
|
||||
title: '',
|
||||
body: '',
|
||||
action: publicRuntimeConfig.push.defaultAction,
|
||||
iconLink: publicRuntimeConfig.push.defaultIconLink,
|
||||
imageLink: publicRuntimeConfig.push.defaultImageLink,
|
||||
target: 'a',
|
||||
topic: '',
|
||||
loading: false,
|
||||
openSnack: false,
|
||||
error: false,
|
||||
warning: false,
|
||||
success: false,
|
||||
snackMessage: ""
|
||||
};
|
||||
|
||||
static async getInitialProps({ req }) {
|
||||
let props = {};
|
||||
|
||||
props.session = await NextAuth.init({ req })
|
||||
|
||||
return props
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
async componentDidMount() {
|
||||
|
||||
}
|
||||
|
||||
getStepContent = (step) => {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return <PushParamForm
|
||||
onSelectedTitle={this.setTitle}
|
||||
onSelectedBody={this.setBody}
|
||||
onSelectedAction={this.setAction}
|
||||
onSelectedImageLink={this.setImageLink}
|
||||
onSelectedIconLink={this.setIconLink}
|
||||
title={this.state.title}
|
||||
body={this.state.body}
|
||||
action={this.state.action}
|
||||
imageLink={this.state.imageLink}
|
||||
iconLink={this.state.iconLink}
|
||||
/>;
|
||||
case 1:
|
||||
return <PushTargetForm
|
||||
onSelectedTarget={this.setTarget}
|
||||
onSelectedTopic={this.setTopic}
|
||||
target={this.state.target}
|
||||
topic={this.state.topic} />;
|
||||
case 2:
|
||||
return <PushReview
|
||||
title={this.state.title}
|
||||
body={this.state.body}
|
||||
action={this.state.action}
|
||||
imageLink={this.state.imageLink}
|
||||
iconLink={this.state.iconLink}
|
||||
target={this.state.target} />;
|
||||
default:
|
||||
throw new Error('Unknown step');
|
||||
}
|
||||
}
|
||||
|
||||
setTitle = (title) => {
|
||||
this.setState(state => ({
|
||||
title
|
||||
}));
|
||||
}
|
||||
|
||||
setBody = (body) => {
|
||||
this.setState(state => ({
|
||||
body
|
||||
}));
|
||||
}
|
||||
|
||||
setAction = (action) => {
|
||||
this.setState(state => ({
|
||||
action
|
||||
}));
|
||||
}
|
||||
setImageLink = (imageLink) => {
|
||||
this.setState(state => ({
|
||||
imageLink
|
||||
}));
|
||||
}
|
||||
setIconLink = (iconLink) => {
|
||||
this.setState(state => ({
|
||||
iconLink
|
||||
}));
|
||||
}
|
||||
setTarget = (target) => {
|
||||
this.setState(state => ({
|
||||
target
|
||||
}));
|
||||
}
|
||||
setTopic = (topic) => {
|
||||
this.setState(state => ({
|
||||
topic
|
||||
}));
|
||||
}
|
||||
async componentDidMount() {
|
||||
//loading lib for client side only
|
||||
let result = await import("../src/backSync.js")
|
||||
back = result.default
|
||||
|
||||
const cookies = new Cookies()
|
||||
cookies.set('redirect_url', window.location.pathname, { path: '/' })
|
||||
}
|
||||
|
||||
sendPush(url, payload) {
|
||||
axios.post(url, {
|
||||
payload
|
||||
}).then(response => {
|
||||
this.setState({ success: true, error: false, warning: false, message: '', snackMessage: "Votre message à bien été envoyé.", loading: false, openSnack: false })
|
||||
}).catch(error => {
|
||||
if (error.status != 500) {
|
||||
if (process.browser) {
|
||||
// client-side-only code
|
||||
back.saveEventDataLocally([{ message: this.state.message }]);
|
||||
}
|
||||
}
|
||||
this.setState({ success: false, error: false, warning: true, snackMessage: "📴 Votre message n'a pas pu aboutir, mais a été sauvegardé. Nous l'enverrons lorsque nous aurons une meilleure connectivité.", loading: false, openSnack: true })
|
||||
});
|
||||
}
|
||||
|
||||
async sendSingleMessage(payload) {
|
||||
let EdeclicLib = (await import('../components/firebase-client.js')).default
|
||||
if (!EdeclicLib.messaging)
|
||||
return
|
||||
payload.message.token = await EdeclicLib.askForToken();
|
||||
|
||||
this.sendPush("/push/message", payload)
|
||||
}
|
||||
|
||||
sendToAll(payload) {
|
||||
this.sendPush("/push/allsubscribers", payload)
|
||||
}
|
||||
|
||||
sendToTopic(payload) {
|
||||
payload.message.topic = this.state.topic
|
||||
this.sendPush("/push/message", payload)
|
||||
}
|
||||
|
||||
handleNext = async () => {
|
||||
"use strict";
|
||||
if (this.state.activeStep == 2) {
|
||||
//TODO: verify infos
|
||||
let payload = {
|
||||
message: {
|
||||
webpush: {
|
||||
headers: {
|
||||
Urgency: "high"
|
||||
},
|
||||
notification: {
|
||||
title: this.state.title,
|
||||
body: this.state.body,
|
||||
icon: this.state.iconLink,
|
||||
image: this.state.imageLink,
|
||||
click_action: this.state.action
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
//single
|
||||
if (this.state.target === 'a') {
|
||||
this.sendSingleMessage(payload)
|
||||
}//to All
|
||||
else if (this.state.target === 'b') {
|
||||
this.sendToAll(payload.message)
|
||||
}//to topics
|
||||
else if (this.state.target === 'c') {
|
||||
this.sendToTopic(payload)
|
||||
}
|
||||
|
||||
}
|
||||
this.setState(state => ({
|
||||
activeStep: state.activeStep + 1,
|
||||
}));
|
||||
};
|
||||
|
||||
handleBack = () => {
|
||||
this.setState(state => ({
|
||||
activeStep: state.activeStep - 1,
|
||||
}));
|
||||
};
|
||||
|
||||
handleReset = () => {
|
||||
this.setState({
|
||||
activeStep: 0,
|
||||
});
|
||||
};
|
||||
|
||||
handleClose = () => {
|
||||
this.setState({ openSnack: false })
|
||||
}
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
const { activeStep } = this.state;
|
||||
|
||||
return (
|
||||
<Layout menu={this.props.menu}>
|
||||
{(this.state.isSignedIn) &&
|
||||
(<Fragment>
|
||||
<main className={classes.layout}>
|
||||
<Paper className={classes.paper}>
|
||||
{this.state.loading && (
|
||||
<div className="alert alert-info">
|
||||
<CircularProgress color="secondary" />Verification en cours...
|
||||
</div>
|
||||
)}
|
||||
<Typography component="h1" variant="h4" align="center">
|
||||
Push Notification Launcher
|
||||
</Typography>
|
||||
<Stepper activeStep={activeStep} className={classes.stepper}>
|
||||
{steps.map(label => (
|
||||
<Step key={label}>
|
||||
<StepLabel>{label}</StepLabel>
|
||||
</Step>
|
||||
))}
|
||||
</Stepper>
|
||||
<Fragment>
|
||||
{activeStep === steps.length ? (
|
||||
<Fragment>
|
||||
<Typography variant="h5" gutterBottom>
|
||||
Votre push a été envoyé sur firebase.
|
||||
</Typography>
|
||||
<Typography variant="subtitle1">
|
||||
Il devrait être soumis dans quelques instants.
|
||||
</Typography>
|
||||
<div className={classes.buttons}>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={this.handleReset} className={classes.button}>
|
||||
Nouveau 🏭
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
) : (
|
||||
<Fragment>
|
||||
{this.getStepContent(activeStep)}
|
||||
<div className={classes.buttons}>
|
||||
{activeStep !== 0 && (
|
||||
<Button onClick={this.handleBack} className={classes.button}>
|
||||
Retour
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={this.handleNext}
|
||||
className={classes.button}
|
||||
>
|
||||
{activeStep === steps.length - 1 ? 'Launch 🚀' : 'Next'}
|
||||
</Button>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</Paper>
|
||||
</main>
|
||||
</Fragment>
|
||||
)}
|
||||
{(!this.state.isSignedIn) &&
|
||||
(<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.button}
|
||||
onClick={() => Router.push('/auth/credentials')}>Sign in to use that feature</Button>)
|
||||
}
|
||||
<Snackbar
|
||||
autoHideDuration={6000}
|
||||
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
|
||||
open={this.state.openSnack}
|
||||
onClose={this.handleClose}
|
||||
>
|
||||
<SnackbarContent
|
||||
className={classes[this.state.error ? 'error' : this.state.warning ? 'warning' : 'success']}
|
||||
message={<span id="message-id">{this.state.snackMessage}</span>}
|
||||
/></Snackbar>
|
||||
</Layout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default withStyles(styles)(PushCreator);
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Example account management routes
|
||||
**/
|
||||
'use strict'
|
||||
|
||||
module.exports = (expressApp, functions) => {
|
||||
|
||||
if (expressApp === null) {
|
||||
throw new Error('expressApp option must be an express server instance')
|
||||
}
|
||||
|
||||
// Expose a route to return user profile if logged in with a session
|
||||
expressApp.get('/account/user', (req, res) => {
|
||||
if (req.user) {
|
||||
functions.find({id: req.user.id})
|
||||
.then(user => {
|
||||
if (!user) return res.status(500).json({error: 'Unable to fetch profile'})
|
||||
res.json({
|
||||
name: user.name,
|
||||
email: user.email,
|
||||
emailVerified: (user.emailVerified && user.emailVerified === true) ? true : false
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
return res.status(500).json({error: 'Unable to fetch profile'})
|
||||
})
|
||||
} else {
|
||||
return res.status(403).json({error: 'Must be signed in to get profile'})
|
||||
}
|
||||
})
|
||||
|
||||
// Expose a route to allow users to update their profiles (name, email)
|
||||
expressApp.post('/account/user', (req, res) => {
|
||||
if (req.user) {
|
||||
functions.find({id: req.user.id})
|
||||
.then(user => {
|
||||
if (!user) return res.status(500).json({error: 'Unable to fetch profile'})
|
||||
|
||||
if (req.body.name)
|
||||
user.name = req.body.name
|
||||
|
||||
if (req.body.email) {
|
||||
// Reset email verification field if email address has changed
|
||||
if (req.body.email && req.body.email !== user.email)
|
||||
user.emailVerified = false
|
||||
|
||||
user.email = req.body.email
|
||||
}
|
||||
return functions.update(user)
|
||||
})
|
||||
.then(user => {
|
||||
return res.status(204).redirect('/account')
|
||||
})
|
||||
.catch(err => {
|
||||
return res.status(500).json({error: 'Unable to fetch profile'})
|
||||
})
|
||||
} else {
|
||||
return res.status(403).json({error: 'Must be signed in to update profile'})
|
||||
}
|
||||
})
|
||||
|
||||
// Expose a route to allow users to delete their profile.
|
||||
expressApp.post('/account/delete', (req, res) => {
|
||||
if (req.user) {
|
||||
functions.remove(req.user.id)
|
||||
.then(() => {
|
||||
// Destroy local session after deleting account
|
||||
req.logout()
|
||||
req.session.destroy(() => {
|
||||
// When the account has been deleted, redirect client to
|
||||
// /auth/callback to ensure the client has it's local session state
|
||||
// updated to reflect that the user is no longer logged in.
|
||||
return res.redirect(`/auth/callback?action=signout`)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
return res.status(500).json({error: 'Unable to delete profile'})
|
||||
})
|
||||
} else {
|
||||
return res.status(403).json({error: 'Must be signed in to delete profile'})
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Defines an endpoint that returns a list of users. You must be signed in and
|
||||
* have "admin": true set in your profile to be able to call the /admin/users
|
||||
* end point (you will need to configure persistant Mongo database to do that).
|
||||
*
|
||||
* Note: These routes only work if you have actually configured a MONGO_URI!
|
||||
* They do not work if you are using the fallback in-memory database.
|
||||
**/
|
||||
'use strict'
|
||||
|
||||
const MongoClient = require('mongodb').MongoClient
|
||||
|
||||
let usersCollection
|
||||
if (process.env.MONGO_URI) {
|
||||
// Connect to MongoDB Database and return user connection
|
||||
MongoClient.connect(process.env.MONGO_URI, (err, mongoClient) => {
|
||||
if (err) throw new Error(err)
|
||||
const dbName = process.env.MONGO_URI.split('/').pop().split('?').shift()
|
||||
const db = mongoClient.db(dbName)
|
||||
usersCollection = db.collection('users')
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = (expressApp) => {
|
||||
|
||||
if (expressApp === null) {
|
||||
throw new Error('expressApp option must be an express server instance')
|
||||
}
|
||||
|
||||
expressApp.get('/admin/users', (req, res) => {
|
||||
// Check user is logged in and has admin access
|
||||
if (!req.user || !req.user.admin || req.user.admin !== true)
|
||||
return res.status('403').end()
|
||||
|
||||
const page = (req.query.page && parseInt(req.query.page) > 0) ? parseInt(req.query.page) : 1
|
||||
const sort = (req.query.sort) ? { [req.query.sort]: 1 } : {}
|
||||
|
||||
let size = 10
|
||||
if (req.query.size
|
||||
&& parseInt(req.query.size) > 0
|
||||
&& parseInt(req.query.size) < 500) {
|
||||
size = parseInt(req.query.size)
|
||||
}
|
||||
|
||||
const skip = (size*(page-1) > 0) ? size*(page-1) : 0
|
||||
|
||||
let response = {
|
||||
users: [],
|
||||
page: page,
|
||||
size: size,
|
||||
sort: req.params.sort,
|
||||
total: 0
|
||||
}
|
||||
|
||||
if (req.params.sort) response.sort = req.params.sort
|
||||
|
||||
let result
|
||||
return new Promise(function(resolve, reject) {
|
||||
result = usersCollection
|
||||
.find()
|
||||
.skip(skip)
|
||||
.sort(sort)
|
||||
.limit(size)
|
||||
|
||||
result.toArray((err, users) => {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(users)
|
||||
}
|
||||
})
|
||||
})
|
||||
.then(users => {
|
||||
response.users = users
|
||||
return result.count()
|
||||
})
|
||||
.then(count => {
|
||||
response.total = count
|
||||
return res.json(response)
|
||||
})
|
||||
.catch(err => {
|
||||
return res.status(500).json(err)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
|
||||
|
||||
require('dotenv').load()
|
||||
const utils = require('./src/utils');
|
||||
const mailer = require("./src/mailer.js")
|
||||
const { google } = require('googleapis');
|
||||
//const express = require('express')
|
||||
const axios = require('axios')
|
||||
const next = require('next')
|
||||
const nextAuth = require('next-auth')
|
||||
const nextAuthConfig = require('./next-auth.config')
|
||||
const { join } = require('path');
|
||||
const { parse } = require('url');
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({ dev })
|
||||
const handle = app.getRequestHandler()
|
||||
|
||||
// const passport = require('passport')
|
||||
// const FacebookStrategy = require('passport-facebook').Strategy
|
||||
|
||||
const routes = {
|
||||
admin: require('./routes/admin'),
|
||||
account: require('./routes/account')
|
||||
}
|
||||
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
function getAccessToken() {
|
||||
return new Promise(function (resolve, reject) {
|
||||
var key = require('./service-account.json');
|
||||
var jwtClient = new google.auth.JWT(
|
||||
key.client_email,
|
||||
null,
|
||||
key.private_key,
|
||||
'https://www.googleapis.com/auth/firebase.messaging',
|
||||
null
|
||||
);
|
||||
jwtClient.authorize(function (err, tokens) {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
|
||||
resolve(tokens.access_token);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// passport.use(new FacebookStrategy({
|
||||
// clientID: process.env.FACEBOOK_ID,
|
||||
// clientSecret: process.env.FACEBOOK_SECRET,
|
||||
// callbackURL: "http://localhost:3000/auth/facebook/callback",
|
||||
// enableProof: true
|
||||
// },
|
||||
// function (accessToken, refreshToken, profile, cb) {
|
||||
// User.findOrCreate({ facebookId: profile.id }, function (err, user) {
|
||||
// return cb(err, user);
|
||||
// });
|
||||
// console.log(profile)
|
||||
|
||||
// console.log("accessToken : " + accessToken);
|
||||
// console.log("refreshToken" + refreshToken);//****not needed because we only use accesstoken to connect to our api*/
|
||||
|
||||
// return cb(null, profile);
|
||||
// }
|
||||
// ));
|
||||
|
||||
app.prepare()
|
||||
.then(() => {
|
||||
// Load configuration and return config object
|
||||
return nextAuthConfig()
|
||||
})
|
||||
.then(nextAuthOptions => {
|
||||
// Pass Next.js App instance and NextAuth options to NextAuth
|
||||
// Note We do not pass a port in nextAuthOptions, because we want to add some
|
||||
// additional routes before Express starts (if you do pass a port, NextAuth
|
||||
// tells NextApp to handle default routing and starts Express automatically).
|
||||
return nextAuth(app, nextAuthOptions)
|
||||
})
|
||||
.then(nextAuthOptions => {
|
||||
|
||||
const express = nextAuthOptions.express
|
||||
const expressApp = nextAuthOptions.expressApp
|
||||
|
||||
// Add admin routes
|
||||
routes.admin(expressApp)
|
||||
|
||||
// Add account management route - reuses functions defined for NextAuth
|
||||
routes.account(expressApp, nextAuthOptions.functions)
|
||||
|
||||
expressApp.use('/', express.static(__dirname + '/.next/'))
|
||||
|
||||
var jsonParser = bodyParser.json()
|
||||
|
||||
expressApp.post('/feedback', jsonParser, (req, res) => {
|
||||
|
||||
let message = req.body.message;
|
||||
let nom = req.body.nom;
|
||||
let prenom = req.body.prenom;
|
||||
let tel = req.body.tel;
|
||||
let mail = req.body.mail;
|
||||
let recaptchaValue = req.body.recaptchaValue;
|
||||
|
||||
console.log(message);
|
||||
if (message === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "Message is empty." }
|
||||
}); return;
|
||||
}
|
||||
if (mail === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "email is empty." }
|
||||
}); return;
|
||||
}
|
||||
utils.verifyCaptcha(recaptchaValue)
|
||||
.then(
|
||||
respo => {
|
||||
if (respo.data.success == true) {
|
||||
try {
|
||||
mailer.sendEmail(message, nom, prenom, tel, mail);
|
||||
res.status(200).send({})
|
||||
} catch (err) {
|
||||
res.status(500).send({ message: "Send mail fail." })
|
||||
}
|
||||
}
|
||||
else
|
||||
res.status(500).send({ message: "Captcha invalid." })
|
||||
})
|
||||
.catch(() =>
|
||||
res.status(403).send({ message: "Captcha verification impossible." })
|
||||
)
|
||||
|
||||
});
|
||||
|
||||
expressApp.post('/push/message', jsonParser, (req, res) => {
|
||||
let message = req.body.payload;
|
||||
|
||||
if (message === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "Message is empty." }
|
||||
}); return;
|
||||
}
|
||||
else {
|
||||
getAccessToken().then((googleAccessToken) => {
|
||||
utils.setAuthorizationHeader(googleAccessToken);
|
||||
// console.log(message);
|
||||
axios.post('https://fcm.googleapis.com/v1/projects/edeclicpwa/messages:send',
|
||||
message
|
||||
).then(r => {
|
||||
console.log("message : "); console.log(r)
|
||||
res.status(200).send({ message: "message sent to firebase" })
|
||||
}
|
||||
)
|
||||
.catch(err => console.log("first catch" + err)
|
||||
//res.status(500).send({ message: "Push error" })
|
||||
)
|
||||
}).catch(err => console.log("second catch" + err)
|
||||
//err => res.status(500).send({ message: "Push error" + err })
|
||||
)
|
||||
|
||||
}
|
||||
})
|
||||
expressApp.get('/push/info/:token', (req, res) => {
|
||||
|
||||
var token = req.params.token
|
||||
if (token === '') {
|
||||
res.status(498).json({
|
||||
errors: { global: "No token specified." }
|
||||
}); return;
|
||||
}
|
||||
|
||||
var url = 'https://iid.googleapis.com/iid/info/' + token + '?details=true'
|
||||
|
||||
utils.setAuthorizationHeaderForIID(process.env.FIREBASE_API_KEY)
|
||||
axios.get(url).then(r => {
|
||||
if (!!r.data.rel && !!r.data.rel.topics) {
|
||||
res.status(200).send({ topics: r.data.rel.topics })
|
||||
}
|
||||
else
|
||||
res.status(200).send({ topics: null })
|
||||
}).catch(err => {
|
||||
console.log(err)
|
||||
res.status(500).send({ message: "Push error" })
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
expressApp.post('/push/subscribe', jsonParser, (req, res) => {
|
||||
let message = req.body;
|
||||
|
||||
if (message === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "Message is empty." }
|
||||
}); return;
|
||||
}
|
||||
else {
|
||||
if (message.topic === '' || message.token === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "Message isnot well formed." }
|
||||
}); return;
|
||||
}
|
||||
|
||||
var url = 'https://iid.googleapis.com/iid/v1/' + message.token + '/rel/topics/' + message.topic
|
||||
|
||||
utils.setAuthorizationHeaderForIID(process.env.FIREBASE_API_KEY)
|
||||
|
||||
axios.post(url)
|
||||
.then(r => {
|
||||
res.status(200).send({ message: "message sent to firebase" })
|
||||
})
|
||||
.catch(err => console.log(err))
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
expressApp.post('/push/unsubscribe', jsonParser, (req, res) => {
|
||||
let message = req.body;
|
||||
|
||||
if (message === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "Message is empty." }
|
||||
}); return;
|
||||
}
|
||||
else {
|
||||
if (message.topic === '' || message.token === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "Message is not well formed." }
|
||||
}); return;
|
||||
}
|
||||
message.topic = '/topics/' + message.topic
|
||||
var payload = { to: message.topic, registration_tokens: [message.token] }
|
||||
var url = 'https://iid.googleapis.com/iid/v1:batchRemove'
|
||||
|
||||
utils.setAuthorizationHeaderForIID(process.env.FIREBASE_API_KEY)
|
||||
axios.post(url, payload
|
||||
).then(r => {
|
||||
res.status(200).send({ message: "message sent to firebase" })
|
||||
}
|
||||
)
|
||||
.catch(err => console.log(err)
|
||||
)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
//https://us-central1-edeclicpwa.cloudfunctions.net/pushMessageToAllSubscribers
|
||||
expressApp.post('/push/allsubscribers', jsonParser, (req, res) => {
|
||||
let message = req.body.payload;
|
||||
|
||||
if (message === '') {
|
||||
res.status(400).json({
|
||||
errors: { global: "Message is empty." }
|
||||
}); return;
|
||||
}
|
||||
else {
|
||||
|
||||
utils.setAuthorizationHeader(process.env.FIREBASE_API_KEY)
|
||||
// console.log(message);
|
||||
axios.post('https://us-central1-edeclicpwa.cloudfunctions.net/pushMessageToAllSubscribers',
|
||||
{ payload: message }
|
||||
).then(r => {
|
||||
res.status(200).send({ message: "message sent to firebase" })
|
||||
}
|
||||
)
|
||||
.catch(err => console.log(err))
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
expressApp.get('/static/js/*.js', (req, res) => {
|
||||
|
||||
const parsedUrl = parse(req.url, true);
|
||||
const { pathname } = parsedUrl;
|
||||
const filePath = join(__dirname, '.next', pathname);
|
||||
app.serveStatic(req, res, filePath);
|
||||
})
|
||||
|
||||
expressApp.get('/manifest.json', (req, res) => {
|
||||
|
||||
const parsedUrl = parse(req.url, true);
|
||||
const { pathname } = parsedUrl;
|
||||
const filePath = join(__dirname, '/static/', pathname);
|
||||
app.serveStatic(req, res, filePath);
|
||||
})
|
||||
|
||||
|
||||
expressApp.get('*', (req, res) => {
|
||||
return handle(req, res)
|
||||
})
|
||||
|
||||
expressApp.listen(process.env.PORT, (err) => {
|
||||
if (err) throw err
|
||||
console.log('> Ready on http://localhost:' + process.env.PORT)
|
||||
console.log('> access Token googleapi >' + getAccessToken())
|
||||
})
|
||||
})
|
||||
.catch((ex) => {
|
||||
console.error(ex.stack)
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"type": "service_account",
|
||||
"project_id": "edeclicpwa",
|
||||
"private_key_id": "aef80b8a8d6f9f10aa0ef0cffd46992399fac45d",
|
||||
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDZuQVADJXtA7Hb\n2R36sZxD3fw5QOmLwvAGnIXtexf/hppz6J1mUDsbNDLF3FxlgzdKQguGE0+q6QI+\nIWEMYPVQPot8Tbs8rcL06EKkhLR3KHRI6hWRyFNnWKnig7xVD5LUNVjgtHNIx9TC\ndf+/F6kSsz7w7cjHZEFZ5k7eAeD3M15EhtGCCyJ9DM3MO/pKzW4lah1jTGeV1ZYS\nimuBSbSX9Z6k6l3dUayMYaARYjZkBu1syhUGs/KoeqS+o+bR1OJX/JiaYe0lWWAg\nuL4315c2SkuXeHX2y9BvxbY2TGfhAPuNFPSsJgt0hXE6QeSIjFZXePmWIq28tNhH\n6OR/EJFzAgMBAAECggEAZ23/YgF4mct3C19V4BnPB+ilYReGuzfkqediskIXUPMD\nXcvkNk4n/hDqi9dW53yR4AuHCO8UmjcuMxDNV0GaWEAWKHuO1tEfPBQ4UIqgZrkH\noPnfPE2j3YUf03VMm0YWNQyQx9LBr5IK70R6NbAKSFFxtafoiVyFtSz1S38t/ZB/\nqigCvK48kG6vZCyAozuKVbkLKorY8THZRbK24Mwp4rnAxRHUxG9pXCGe1/j1FvSP\nPRGPptvRSLP2kRPZXELKga7/yCPIOqT2jEoWiXAdfU2gyED4jEhby+ryBHuTsxpo\nJiaGtbUWMKjOEUOWekgTMvtgQD4T5WP5zx2AAZ5K4QKBgQDz7075SYWTy98MZSC2\nMVF6Ei2mGDMxLDJqpTnc3Oi7+eOXI8uTo5SqgaBHfM/uTywcipqSUQTseDN7C1wu\nWOb6r0shK6FfDP1GQc9l0mmNuIZ8JLkc6nB8Es5wwd4vLRhuSsy6pSz/wDovC3Q2\n5se/B7ir+kYeFlhIMAjN91rukwKBgQDkfdDpAociVjFpcjly5Zf60uL/BzIZOdpn\n1Iq/kAxFr2kuZzGDpwUgXvSU4nELivrsf452x3+R78C2SWwil9IRrD3DAYJzT3qP\nb6l4EuKuHRPtVJBqIPWaMiT6Ov6aAQpGYh5qZjFeHSIDBzXHd/OBbSzisE3zTOqm\nED3/eQq9oQKBgAD/XYdPcahlEQhv8W5NTVP+dwlS2AK/d4VQH6hzjtAV+YRItTBp\nXtZDqXAhZohG8ps7Rd6LTkXZR/yc00etPWSRCvGbyBEncHG1GzADaEMYGhSv4cHo\ng4U+XnG/mTUALjVlQOkSe9if5J0EovkGgJKbaXnqkBbXaI0DBUYyWMDZAoGBAMND\nGfLmbCFV02gvaxTbTCPXcJFMzu1r2U99/Qxzx2kN3C8BlPjTFLhzLUTGtqCMpp7Q\n6yhqmIRYhTHCURzG7YiYzzcE5TwxoaVOYV7xlLICu3LIH5nyjLC3RY5qOAXX+bXo\nR+HZbzrkXpqD4NuTkI78g609yX+wLZ64pqLaB+nBAoGATMflJNxgyWtY6b2yFYgm\nCOhMecJKMS2Rgmdl3FQGxAMPMIy18URW/t99LmbUhqQGsdFPDTcxR6V4ajWqltrj\nLkbrEtSHp7xFAQD2anUeEwxtW8PpICwhgGXJUSjXU8+2A20CaWcTwH5USnNp8kp/\n9402JWBkoe+kodILFHZ363M=\n-----END PRIVATE KEY-----\n",
|
||||
"client_email": "firebase-adminsdk-hffwl@edeclicpwa.iam.gserviceaccount.com",
|
||||
"client_id": "100436348289421185617",
|
||||
"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_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-hffwl%40edeclicpwa.iam.gserviceaccount.com"
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
|
||||
import idb from "./idb-promised.js"
|
||||
|
||||
|
||||
const dbPromise = createIndexedDB();
|
||||
|
||||
function createIndexedDB() {
|
||||
if (!('indexedDB' in window)) { return null; }
|
||||
return idb.open('dashboardr', 1, function (upgradeDb) {
|
||||
if (!upgradeDb.objectStoreNames.contains('events')) {
|
||||
const eventsOS = upgradeDb.createObjectStore('events', { keyPath: 'id' });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const backSync = {
|
||||
saveEventDataLocally: (events) => {
|
||||
if (!('indexedDB' in window)) {
|
||||
return null;
|
||||
}
|
||||
return dbPromise.then(db => {
|
||||
const tx = db.transaction('events', 'readwrite');
|
||||
const store = tx.objectStore('events');
|
||||
return Promise.all(events.map(event => store.put(event)))
|
||||
.catch(() => {
|
||||
tx.abort();
|
||||
throw Error('Events were not added to the store');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default backSync;
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import lscache from 'lscache';
|
||||
import fetch from 'isomorphic-unfetch';
|
||||
|
||||
const TTL_MINUTES = 5;
|
||||
|
||||
export default async function(url, options) {
|
||||
// We don't cache anything when server-side rendering.
|
||||
// That way if users refresh the page they always get fresh data.
|
||||
if (typeof window === 'undefined') {
|
||||
return fetch(url, options).then(response => response.json());
|
||||
}
|
||||
|
||||
let cachedResponse = lscache.get(url);
|
||||
|
||||
// If there is no cached response,
|
||||
// do the actual call and store the response
|
||||
if (cachedResponse === null) {
|
||||
cachedResponse = await fetch(url, options)
|
||||
.then(response => response.json());
|
||||
lscache.set(url, cachedResponse, TTL_MINUTES);
|
||||
}
|
||||
|
||||
return cachedResponse;
|
||||
}
|
||||
|
||||
export function overrideCache(key, val) {
|
||||
|
||||
lscache.set(key, val, TTL_MINUTES);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/* eslint-disable no-underscore-dangle */
|
||||
|
||||
import { SheetsRegistry } from 'jss';
|
||||
import { createMuiTheme, createGenerateClassName } from '@material-ui/core/styles';
|
||||
import blue from '@material-ui/core/colors/blue';
|
||||
import grey from '@material-ui/core/colors/grey';
|
||||
import purple from '@material-ui/core/colors/purple';
|
||||
import green from '@material-ui/core/colors/green';
|
||||
// A theme with custom primary and secondary color.
|
||||
// It's optional.
|
||||
const theme = createMuiTheme({
|
||||
palette: {
|
||||
primary: {
|
||||
main: '#ffffff',
|
||||
},
|
||||
secondary: {
|
||||
main: '#333',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
useNextVariants: true,
|
||||
// Use the system font instead of the default Roboto font.
|
||||
fontFamily: [
|
||||
'-apple-system',
|
||||
'BlinkMacSystemFont',
|
||||
'"Segoe UI"',
|
||||
'Roboto',
|
||||
'"Helvetica Neue"',
|
||||
'Arial',
|
||||
'sans-serif',
|
||||
'"Apple Color Emoji"',
|
||||
'"Segoe UI Emoji"',
|
||||
'"Segoe UI Symbol"',
|
||||
].join(','),
|
||||
},
|
||||
overrides: {
|
||||
MuiButton: {
|
||||
root:{
|
||||
color: 'black', // Some CSS
|
||||
}
|
||||
|
||||
},
|
||||
MuiStepIcon:{ completed:{
|
||||
color:'#43a047!important'
|
||||
},
|
||||
active:{
|
||||
color:'#43a047!important'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
function createPageContext() {
|
||||
return {
|
||||
theme,
|
||||
// This is needed in order to deduplicate the injection of CSS in the page.
|
||||
sheetsManager: new Map(),
|
||||
// This is needed in order to inject the critical CSS.
|
||||
sheetsRegistry: new SheetsRegistry(),
|
||||
// The standard class name generator.
|
||||
generateClassName: createGenerateClassName(),
|
||||
};
|
||||
}
|
||||
|
||||
export default function getPageContext() {
|
||||
// Make sure to create a new context for every server-side request so that data
|
||||
// isn't shared between connections (which would be bad).
|
||||
if (!process.browser) {
|
||||
return createPageContext();
|
||||
}
|
||||
|
||||
// Reuse context on the client-side.
|
||||
if (!global.__INIT_MATERIAL_UI__) {
|
||||
global.__INIT_MATERIAL_UI__ = createPageContext();
|
||||
}
|
||||
|
||||
return global.__INIT_MATERIAL_UI__;
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
Copyright 2018 Google Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
'use strict';
|
||||
(function() {
|
||||
function toArray(arr) {
|
||||
return Array.prototype.slice.call(arr);
|
||||
}
|
||||
|
||||
function promisifyRequest(request) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
request.onsuccess = function() {
|
||||
resolve(request.result);
|
||||
};
|
||||
|
||||
request.onerror = function() {
|
||||
reject(request.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function promisifyRequestCall(obj, method, args) {
|
||||
var request;
|
||||
var p = new Promise(function(resolve, reject) {
|
||||
request = obj[method].apply(obj, args);
|
||||
promisifyRequest(request).then(resolve, reject);
|
||||
});
|
||||
|
||||
p.request = request;
|
||||
return p;
|
||||
}
|
||||
|
||||
function promisifyCursorRequestCall(obj, method, args) {
|
||||
var p = promisifyRequestCall(obj, method, args);
|
||||
return p.then(function(value) {
|
||||
if (!value) return;
|
||||
return new Cursor(value, p.request);
|
||||
});
|
||||
}
|
||||
|
||||
function proxyProperties(ProxyClass, targetProp, properties) {
|
||||
properties.forEach(function(prop) {
|
||||
Object.defineProperty(ProxyClass.prototype, prop, {
|
||||
get: function() {
|
||||
return this[targetProp][prop];
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function proxyRequestMethods(ProxyClass, targetProp, Constructor, properties) {
|
||||
properties.forEach(function(prop) {
|
||||
if (!(prop in Constructor.prototype)) return;
|
||||
ProxyClass.prototype[prop] = function() {
|
||||
return promisifyRequestCall(this[targetProp], prop, arguments);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function proxyMethods(ProxyClass, targetProp, Constructor, properties) {
|
||||
properties.forEach(function(prop) {
|
||||
if (!(prop in Constructor.prototype)) return;
|
||||
ProxyClass.prototype[prop] = function() {
|
||||
return this[targetProp][prop].apply(this[targetProp], arguments);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function proxyCursorRequestMethods(ProxyClass, targetProp, Constructor, properties) {
|
||||
properties.forEach(function(prop) {
|
||||
if (!(prop in Constructor.prototype)) return;
|
||||
ProxyClass.prototype[prop] = function() {
|
||||
return promisifyCursorRequestCall(this[targetProp], prop, arguments);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function Index(index) {
|
||||
this._index = index;
|
||||
}
|
||||
|
||||
proxyProperties(Index, '_index', [
|
||||
'name',
|
||||
'keyPath',
|
||||
'multiEntry',
|
||||
'unique'
|
||||
]);
|
||||
|
||||
proxyRequestMethods(Index, '_index', IDBIndex, [
|
||||
'get',
|
||||
'getKey',
|
||||
'getAll',
|
||||
'getAllKeys',
|
||||
'count'
|
||||
]);
|
||||
|
||||
proxyCursorRequestMethods(Index, '_index', IDBIndex, [
|
||||
'openCursor',
|
||||
'openKeyCursor'
|
||||
]);
|
||||
|
||||
function Cursor(cursor, request) {
|
||||
this._cursor = cursor;
|
||||
this._request = request;
|
||||
}
|
||||
|
||||
proxyProperties(Cursor, '_cursor', [
|
||||
'direction',
|
||||
'key',
|
||||
'primaryKey',
|
||||
'value'
|
||||
]);
|
||||
|
||||
proxyRequestMethods(Cursor, '_cursor', IDBCursor, [
|
||||
'update',
|
||||
'delete'
|
||||
]);
|
||||
|
||||
// proxy 'next' methods
|
||||
['advance', 'continue', 'continuePrimaryKey'].forEach(function(methodName) {
|
||||
if (!(methodName in IDBCursor.prototype)) return;
|
||||
Cursor.prototype[methodName] = function() {
|
||||
var cursor = this;
|
||||
var args = arguments;
|
||||
return Promise.resolve().then(function() {
|
||||
cursor._cursor[methodName].apply(cursor._cursor, args);
|
||||
return promisifyRequest(cursor._request).then(function(value) {
|
||||
if (!value) return;
|
||||
return new Cursor(value, cursor._request);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
function ObjectStore(store) {
|
||||
this._store = store;
|
||||
}
|
||||
|
||||
ObjectStore.prototype.createIndex = function() {
|
||||
return new Index(this._store.createIndex.apply(this._store, arguments));
|
||||
};
|
||||
|
||||
ObjectStore.prototype.index = function() {
|
||||
return new Index(this._store.index.apply(this._store, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(ObjectStore, '_store', [
|
||||
'name',
|
||||
'keyPath',
|
||||
'indexNames',
|
||||
'autoIncrement'
|
||||
]);
|
||||
|
||||
proxyRequestMethods(ObjectStore, '_store', IDBObjectStore, [
|
||||
'put',
|
||||
'add',
|
||||
'delete',
|
||||
'clear',
|
||||
'get',
|
||||
'getAll',
|
||||
'getAllKeys',
|
||||
'count'
|
||||
]);
|
||||
|
||||
proxyCursorRequestMethods(ObjectStore, '_store', IDBObjectStore, [
|
||||
'openCursor',
|
||||
'openKeyCursor'
|
||||
]);
|
||||
|
||||
proxyMethods(ObjectStore, '_store', IDBObjectStore, [
|
||||
'deleteIndex'
|
||||
]);
|
||||
|
||||
function Transaction(idbTransaction) {
|
||||
this._tx = idbTransaction;
|
||||
this.complete = new Promise(function(resolve, reject) {
|
||||
idbTransaction.oncomplete = function() {
|
||||
resolve();
|
||||
};
|
||||
idbTransaction.onerror = function() {
|
||||
reject(idbTransaction.error);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
Transaction.prototype.objectStore = function() {
|
||||
return new ObjectStore(this._tx.objectStore.apply(this._tx, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(Transaction, '_tx', [
|
||||
'objectStoreNames',
|
||||
'mode'
|
||||
]);
|
||||
|
||||
proxyMethods(Transaction, '_tx', IDBTransaction, [
|
||||
'abort'
|
||||
]);
|
||||
|
||||
function UpgradeDB(db, oldVersion, transaction) {
|
||||
this._db = db;
|
||||
this.oldVersion = oldVersion;
|
||||
this.transaction = new Transaction(transaction);
|
||||
}
|
||||
|
||||
UpgradeDB.prototype.createObjectStore = function() {
|
||||
return new ObjectStore(this._db.createObjectStore.apply(this._db, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(UpgradeDB, '_db', [
|
||||
'name',
|
||||
'version',
|
||||
'objectStoreNames'
|
||||
]);
|
||||
|
||||
proxyMethods(UpgradeDB, '_db', IDBDatabase, [
|
||||
'deleteObjectStore',
|
||||
'close'
|
||||
]);
|
||||
|
||||
function DB(db) {
|
||||
this._db = db;
|
||||
}
|
||||
|
||||
DB.prototype.transaction = function() {
|
||||
return new Transaction(this._db.transaction.apply(this._db, arguments));
|
||||
};
|
||||
|
||||
proxyProperties(DB, '_db', [
|
||||
'name',
|
||||
'version',
|
||||
'objectStoreNames'
|
||||
]);
|
||||
|
||||
proxyMethods(DB, '_db', IDBDatabase, [
|
||||
'close'
|
||||
]);
|
||||
|
||||
// Add cursor iterators
|
||||
// TODO: remove this once browsers do the right thing with promises
|
||||
['openCursor', 'openKeyCursor'].forEach(function(funcName) {
|
||||
[ObjectStore, Index].forEach(function(Constructor) {
|
||||
Constructor.prototype[funcName.replace('open', 'iterate')] = function() {
|
||||
var args = toArray(arguments);
|
||||
var callback = args[args.length - 1];
|
||||
var request = (this._store || this._index)[funcName].apply(this._store, args.slice(0, -1));
|
||||
request.onsuccess = function() {
|
||||
callback(request.result);
|
||||
};
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// polyfill getAll
|
||||
[Index, ObjectStore].forEach(function(Constructor) {
|
||||
if (Constructor.prototype.getAll) return;
|
||||
Constructor.prototype.getAll = function(query, count) {
|
||||
var instance = this;
|
||||
var items = [];
|
||||
|
||||
return new Promise(function(resolve) {
|
||||
instance.iterateCursor(query, function(cursor) {
|
||||
if (!cursor) {
|
||||
resolve(items);
|
||||
return;
|
||||
}
|
||||
items.push(cursor.value);
|
||||
|
||||
if (count !== undefined && items.length == count) {
|
||||
resolve(items);
|
||||
return;
|
||||
}
|
||||
cursor.continue();
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
|
||||
var exp = {
|
||||
open: function(name, version, upgradeCallback) {
|
||||
var p = promisifyRequestCall(indexedDB, 'open', [name, version]);
|
||||
var request = p.request;
|
||||
|
||||
request.onupgradeneeded = function(event) {
|
||||
if (upgradeCallback) {
|
||||
upgradeCallback(new UpgradeDB(request.result, event.oldVersion, request.transaction));
|
||||
}
|
||||
};
|
||||
|
||||
return p.then(function(db) {
|
||||
return new DB(db);
|
||||
});
|
||||
},
|
||||
delete: function(name) {
|
||||
return promisifyRequestCall(indexedDB, 'deleteDatabase', [name]);
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof module !== 'undefined') {
|
||||
module.exports = exp;
|
||||
}
|
||||
else {
|
||||
self.idb = exp;
|
||||
}
|
||||
}());
|
||||
@@ -0,0 +1,40 @@
|
||||
const nodemailer = require("nodemailer");
|
||||
|
||||
|
||||
function setup() {
|
||||
return nodemailer.createTransport({
|
||||
host: process.env.EMAIL_SERVER,
|
||||
port: process.env.EMAIL_PORT,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USERNAME,
|
||||
pass: process.env.EMAIL_PASSWORD
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
exports.sendEmail = function(text,nom,prenom,tel,mail) {
|
||||
const tranport = setup();
|
||||
const email = {
|
||||
from: process.env.EMAIL_FROM,
|
||||
to: "thomas.berrod@e-declic.com",
|
||||
subject: "PWA feedback 💌⚡️",
|
||||
text: `
|
||||
Hello Team,
|
||||
A new user sent a feedback 👌:
|
||||
--->
|
||||
${text}
|
||||
|
||||
nom: ${nom}
|
||||
prenom:${prenom}
|
||||
mail:${mail}
|
||||
tel:${tel}
|
||||
|
||||
<---
|
||||
Have a nice day !
|
||||
- e-declic Team PWA
|
||||
|
||||
`
|
||||
};
|
||||
|
||||
tranport.sendMail(email);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
const axios = require('axios')
|
||||
exports.verifyCaptcha = (recaptchaValue) =>
|
||||
{
|
||||
const verifyUrl = process.env.RECAPTCHA_VERIFY_URL;
|
||||
const ReCAPTCHASecret = process.env.RECAPTCHA_SECRET
|
||||
return axios.get(verifyUrl, {
|
||||
params: { secret: ReCAPTCHASecret, response: recaptchaValue }
|
||||
})
|
||||
|
||||
};
|
||||
|
||||
exports.setAuthorizationHeader = (token = null) => {
|
||||
if (token) {
|
||||
axios.defaults.headers.common.authorization = `Bearer ${token}`;
|
||||
} else {
|
||||
delete axios.defaults.headers.common.authorization;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.setAuthorizationHeaderForIID = (token = null) => {
|
||||
if (token) {
|
||||
axios.defaults.headers.common.authorization = `key=${token}`;
|
||||
} else {
|
||||
delete axios.defaults.headers.common.authorization;
|
||||
}
|
||||
};
|
||||
|
||||
//https://www.google.com/recaptcha/api/siteverify?secret=6LeNK3QUAAAAACR2Y6Rx98hBKTtomtjETRqffKPM&response=03AMGVjXjw6EQMFg9ZIqwT8OEXgupun-bYxZRZ6iC6SNpO2YRw1cfFBi6eMKelpTgE8l91__TCCowcUq4vhIr890XSs2WSTTx_hFNAW2YFFY6kxjIT9U2EIWp47F7GqQGwcJBtogIok7gDPS4qp1qqk37fUiIQnvtCJUCwCDnGkyr0_ecxixq4tR0pl9wu56zwCRi-WXFiZhvN7rCzZbbpL7TQfJvh5Mz3JqnV2WbtR4MP-YIJl226Ec7BZewqbSFYYl6alkR2JuXN5i8lWhRYxa6VQcDb_vPEPw
|
||||
@@ -0,0 +1,76 @@
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, helvetica neue, helvetica, ubuntu,
|
||||
roboto, noto, segoe ui, arial, sans-serif;
|
||||
background: transparent;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#root {
|
||||
overflow: hidden;
|
||||
background: #f0f0f0;
|
||||
}
|
||||
|
||||
.card1,
|
||||
.card2,
|
||||
.card3,
|
||||
.card4 {
|
||||
position: absolute;
|
||||
border-radius: 5px;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.card1 {
|
||||
min-width: 60ch;
|
||||
min-height: 60ch;
|
||||
width: 45vw;
|
||||
height: 45vw;
|
||||
max-width: 100ch;
|
||||
max-height: 100ch;
|
||||
background-image: url(https://image.flaticon.com/icons/svg/119/119596.svg);
|
||||
}
|
||||
|
||||
.card2 {
|
||||
width: 25ch;
|
||||
height: 25ch;
|
||||
background-image: url(https://image.flaticon.com/icons/svg/789/789395.svg);
|
||||
}
|
||||
|
||||
.card3 {
|
||||
opacity: 0.9;
|
||||
width: 25ch;
|
||||
height: 25ch;
|
||||
background-image: url(https://image.flaticon.com/icons/svg/414/414927.svg);
|
||||
}
|
||||
|
||||
.card4 {
|
||||
width: 25ch;
|
||||
height: 25ch;
|
||||
background-image: url(https://image.flaticon.com/icons/svg/789/789392.svg);
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
.cell {
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
color: #777777;
|
||||
text-transform: uppercase;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0px 10px 60px -10px rgba(0, 0, 0, 0.2);
|
||||
transition: box-shadow 0.5s;
|
||||
font-size: 10px;
|
||||
line-height: 10px;
|
||||
}
|
||||
|
||||
.default {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
justify-content: center;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.cell:hover {
|
||||
box-shadow: 0px 20px 60px -10px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.shuffle {
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: 40px;
|
||||
padding: 0px 20px 0px 20px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
background: white;
|
||||
border: solid 2px #ffaad4;
|
||||
border-radius: 7px;
|
||||
color: #ffaad4;
|
||||
z-index: 100000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
font-weight: 100;
|
||||
line-height: 45px;
|
||||
}
|
||||
|
||||
.shuffle:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.details {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #ffffffa0;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
font-weight: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.details h1 {
|
||||
color: #ca6a9a;
|
||||
font-size: 18px;
|
||||
line-height: 18px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
max-width: 50px;
|
||||
}
|
||||
|
||||
.details p {
|
||||
color: #777777;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
margin: 0;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.circle {
|
||||
position: absolute;
|
||||
top: calc(50% - 50px);
|
||||
left: calc(50% - 50px);
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0px 20px 60px -10px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
font-size: 20px;
|
||||
color: #777777;
|
||||
}
|
||||
|
||||
.main {
|
||||
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir,
|
||||
helvetica neue, helvetica, ubuntu, roboto, noto, segoe ui, arial, sans-serif;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 30px 70px 30px 70px;
|
||||
height: auto;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.grid {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.ant-btn-primary {
|
||||
background-color: #ca6a9a !important;
|
||||
border-color: #ca6a9a !important;
|
||||
}
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 118 KiB |
|
After Width: | Height: | Size: 171 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 270 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 506 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 128 KiB |
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="57px" height="57px" viewBox="0 0 57 57" enable-background="new 0 0 57 57" xml:space="preserve">
|
||||
<image overflow="visible" width="512" height="512" xlink:href="../../Downloads/e-declicIco512x512.png" transform="matrix(0.1113 0 0 0.1113 0 -4.882812e-04)">
|
||||
</image>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 644 B |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 6.8 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 7.0 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
After Width: | Height: | Size: 32 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 9.8 KiB |
|
After Width: | Height: | Size: 13 KiB |
@@ -0,0 +1,85 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="330px" height="373px" viewBox="0 0 330 373" enable-background="new 0 0 330 373" xml:space="preserve">
|
||||
<g id="XMLID_2_">
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M297.26,213.2V260.1H286.98V213.2c-0.01-10.091-4.24-19.721-11.66-26.551L235.54,150.2v-13.94
|
||||
l46.711,42.81C291.81,187.851,297.25,200.221,297.26,213.2z"/>
|
||||
<path fill="#FFFFFF" d="M286.979,260.101h10.279c0,68.75-59.221,79.02-92.579,79.02v-9.21c0-0.38,0-0.76,0.01-1.14
|
||||
C219.6,328.771,286.979,325.421,286.979,260.101z"/>
|
||||
<path fill="#383D4C" d="M286.979,213.2V260.1c0,65.32-67.38,68.67-82.29,68.67c0.189-22.77,7.029-45,19.7-63.96
|
||||
c1.359-2.04,1.09-4.75-0.641-6.49l-36.011-36c-3.33-3.329-5.09-7.92-4.859-12.619c0.229-4.699,2.439-9.091,6.08-12.069
|
||||
c7.01-5.36,16.931-4.602,23.029,1.77l50.771,50.78l7.279-7.28l-34.5-34.51v-58.189l39.78,36.449
|
||||
C282.739,193.48,286.97,203.11,286.979,213.2z"/>
|
||||
<path fill="#FFFFFF" d="M227.789,143.101l7.75,7.101v58.189l34.5,34.51l-7.279,7.28l-50.771-50.78
|
||||
c-6.1-6.37-16.02-7.13-23.029-1.77c-3.641,2.979-5.851,7.37-6.08,12.069s1.529,9.289,4.859,12.619l36.011,36
|
||||
c1.729,1.74,2,4.45,0.641,6.49c-12.67,18.96-19.511,41.19-19.7,63.96c-0.01,0.38-0.01,0.76-0.01,1.14v9.21H76.08
|
||||
c-14.189-0.02-25.7-11.528-25.71-25.72V56.21c0.01-14.2,11.521-25.7,25.71-25.721h133.74c14.2,0.021,25.7,11.521,25.72,25.721
|
||||
v80.05l-0.81-0.74L227.789,143.101z M225.25,198.11V71.641H60.649v216.04H189.25v10.29H60.649v15.43
|
||||
c0,8.521,6.91,15.431,15.43,15.431h118.351c0.159-23.391,6.779-46.289,19.108-66.159l-33.069-33.08
|
||||
c-5.38-5.38-8.239-12.79-7.869-20.391c0.369-7.601,3.938-14.689,9.819-19.521c11.109-8.69,26.979-7.64,36.851,2.439L225.25,198.11
|
||||
z M225.25,61.351v-5.14c0-8.521-6.91-15.431-15.431-15.431H76.08c-8.52,0-15.43,6.91-15.43,15.431v5.14H225.25z"/>
|
||||
<polygon fill="#FFFFFF" points="235.539,136.261 235.539,150.201 227.789,143.101 234.729,135.521 "/>
|
||||
<path fill="#383D4C" d="M225.25,71.641v126.47l-5.979-5.989c-9.87-10.08-25.74-11.129-36.851-2.439
|
||||
c-5.881,4.83-9.449,11.92-9.819,19.521s2.489,15.011,7.87,20.391l33.068,33.08c-12.329,19.87-18.949,42.77-19.108,66.159H76.08
|
||||
c-8.52,0-15.43-6.909-15.43-15.431v-15.43h128.6v-10.29H60.649V71.641H225.25z M214.96,164.23v-20.58
|
||||
c0-5.68-4.601-10.279-10.279-10.279H184.1c-5.681,0-10.28,4.6-10.28,10.279v20.58c0,5.681,4.601,10.29,10.28,10.29h20.58
|
||||
C210.359,174.521,214.96,169.911,214.96,164.23z M214.96,112.791v-20.57c0-5.69-4.601-10.29-10.279-10.29H184.1
|
||||
c-5.681,0-10.28,4.6-10.28,10.29v20.57c0,5.689,4.601,10.29,10.28,10.29h20.58C210.359,123.081,214.96,118.48,214.96,112.791z
|
||||
M163.53,267.101v-20.57c0-5.68-4.61-10.289-10.29-10.289h-20.57c-5.689,0-10.29,4.609-10.29,10.289v20.57
|
||||
c0,5.69,4.601,10.29,10.29,10.29h20.57C158.919,277.391,163.53,272.791,163.53,267.101z M163.53,215.671v-20.58
|
||||
c0-5.68-4.61-10.29-10.29-10.29h-20.57c-5.689,0-10.29,4.61-10.29,10.29v20.58c0,5.68,4.601,10.279,10.29,10.279h20.57
|
||||
C158.919,225.95,163.53,221.351,163.53,215.671z M163.53,164.23v-20.58c0-5.68-4.61-10.279-10.29-10.279h-20.57
|
||||
c-5.689,0-10.29,4.6-10.29,10.279v20.58c0,5.681,4.601,10.29,10.29,10.29h20.57C158.919,174.521,163.53,169.911,163.53,164.23z
|
||||
M163.53,112.791v-20.57c0-5.69-4.61-10.29-10.29-10.29h-20.57c-5.689,0-10.29,4.6-10.29,10.29v20.57
|
||||
c0,5.689,4.601,10.29,10.29,10.29h20.57C158.919,123.081,163.53,118.48,163.53,112.791z M148.1,318.54v-10.29h-10.29v10.29H148.1z
|
||||
M112.089,267.101v-20.57c0-5.68-4.6-10.289-10.29-10.289H81.229c-5.681,0-10.29,4.609-10.29,10.289v20.57
|
||||
c0,5.69,4.609,10.29,10.29,10.29h20.569C107.49,277.391,112.089,272.791,112.089,267.101z M112.089,215.671v-20.58
|
||||
c0-5.68-4.6-10.29-10.29-10.29H81.229c-5.681,0-10.29,4.61-10.29,10.29v20.58c0,5.68,4.609,10.279,10.29,10.279h20.569
|
||||
C107.49,225.95,112.089,221.351,112.089,215.671z M112.089,164.23v-20.58c0-5.68-4.6-10.279-10.29-10.279H81.229
|
||||
c-5.681,0-10.29,4.6-10.29,10.279v20.58c0,5.681,4.609,10.29,10.29,10.29h20.569C107.49,174.521,112.089,169.911,112.089,164.23z
|
||||
M112.089,112.791v-20.57c0-5.69-4.6-10.29-10.29-10.29H81.229c-5.681,0-10.29,4.6-10.29,10.29v20.57
|
||||
c0,5.689,4.609,10.29,10.29,10.29h20.569C107.49,123.081,112.089,118.48,112.089,112.791z"/>
|
||||
<path fill="#383D4C" d="M225.25,56.21v5.14H60.649v-5.14c0-8.521,6.91-15.431,15.43-15.431h133.74
|
||||
C218.34,40.781,225.25,47.69,225.25,56.21z"/>
|
||||
<path fill="#FFFFFF" d="M214.96,143.65v20.58c0,5.681-4.601,10.29-10.279,10.29H184.1c-5.681,0-10.28-4.609-10.28-10.29v-20.58
|
||||
c0-5.68,4.601-10.279,10.28-10.279h20.58C210.359,133.371,214.96,137.971,214.96,143.65z"/>
|
||||
<path fill="#FFFFFF" d="M214.96,92.221v20.57c0,5.689-4.601,10.29-10.279,10.29H184.1c-5.681,0-10.28-4.601-10.28-10.29v-20.57
|
||||
c0-5.69,4.601-10.29,10.28-10.29h20.58C210.359,81.931,214.96,86.531,214.96,92.221z"/>
|
||||
<path fill="#FFFFFF" d="M163.53,246.53v20.57c0,5.69-4.61,10.29-10.29,10.29h-20.57c-5.689,0-10.29-4.6-10.29-10.29v-20.57
|
||||
c0-5.68,4.601-10.29,10.29-10.29h20.57C158.919,236.24,163.53,240.851,163.53,246.53z"/>
|
||||
<path fill="#FFFFFF" d="M163.53,195.091v20.58c0,5.68-4.61,10.279-10.29,10.279h-20.57c-5.689,0-10.29-4.601-10.29-10.279v-20.58
|
||||
c0-5.68,4.601-10.29,10.29-10.29h20.57C158.919,184.801,163.53,189.411,163.53,195.091z"/>
|
||||
<path fill="#FFFFFF" d="M163.53,143.65v20.58c0,5.681-4.61,10.29-10.29,10.29h-20.57c-5.689,0-10.29-4.609-10.29-10.29v-20.58
|
||||
c0-5.68,4.601-10.279,10.29-10.279h20.57C158.919,133.371,163.53,137.971,163.53,143.65z"/>
|
||||
<path fill="#FFFFFF" d="M163.53,92.221v20.57c0,5.689-4.61,10.29-10.29,10.29h-20.57c-5.689,0-10.29-4.601-10.29-10.29v-20.57
|
||||
c0-5.69,4.601-10.29,10.29-10.29h20.57C158.919,81.931,163.53,86.531,163.53,92.221z"/>
|
||||
<rect x="137.81" y="308.251" fill="#FFFFFF" width="10.29" height="10.29"/>
|
||||
<path fill="#FFFFFF" d="M112.089,246.53v20.57c0,5.69-4.6,10.29-10.29,10.29H81.229c-5.681,0-10.29-4.6-10.29-10.29v-20.57
|
||||
c0-5.68,4.609-10.29,10.29-10.29h20.569C107.49,236.24,112.089,240.851,112.089,246.53z"/>
|
||||
<path fill="#FFFFFF" d="M112.089,195.091v20.58c0,5.68-4.6,10.279-10.29,10.279H81.229c-5.681,0-10.29-4.601-10.29-10.279v-20.58
|
||||
c0-5.68,4.609-10.29,10.29-10.29h20.569C107.49,184.801,112.089,189.411,112.089,195.091z"/>
|
||||
<path fill="#FFFFFF" d="M112.089,143.65v20.58c0,5.681-4.6,10.29-10.29,10.29H81.229c-5.681,0-10.29-4.609-10.29-10.29v-20.58
|
||||
c0-5.68,4.609-10.279,10.29-10.279h20.569C107.49,133.371,112.089,137.971,112.089,143.65z"/>
|
||||
<path fill="#FFFFFF" d="M112.089,92.221v20.57c0,5.689-4.6,10.29-10.29,10.29H81.229c-5.681,0-10.29-4.601-10.29-10.29v-20.57
|
||||
c0-5.69,4.609-10.29,10.29-10.29h20.569C107.49,81.931,112.089,86.531,112.089,92.221z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="XMLID_1_">
|
||||
<g>
|
||||
<path fill="#FFFFFF" d="M202.769,155.37H191.58c-0.01-0.26-0.01-0.47-0.01-0.59c0-0.88,0.399-1.33,1.04-1.49l5.189-0.74
|
||||
l0.771-0.12l-0.24-0.33c-0.86-1.21-2.271-1.93-3.75-1.93c-2.54,0-4.61,2.07-4.61,4.61c0,2.54,2.07,4.61,4.61,4.61
|
||||
c1.72,0,3.31-0.98,4.1-2.5h3.891c-0.44,1.68-1.431,3.2-2.79,4.31c-1.46,1.19-3.311,1.84-5.2,1.84c-4.55,0-8.26-3.7-8.26-8.26
|
||||
c0-4.55,3.71-8.26,8.26-8.26c4.56,0,8.26,3.71,8.26,8.26c0,0.19,0,0.38-0.02,0.58L202.769,155.37z"/>
|
||||
<path fill="#383D4C" d="M194.58,159.39c-2.54,0-4.61-2.07-4.61-4.61c0-2.54,2.07-4.61,4.61-4.61c1.479,0,2.89,0.72,3.75,1.93
|
||||
l0.24,0.33l-0.771,0.12l-5.189,0.74c-0.641,0.16-1.04,0.61-1.04,1.49c0,0.12,0,0.33,0.01,0.59h11.189l0.051-0.01
|
||||
c0.02-0.2,0.02-0.39,0.02-0.58c0-4.55-3.7-8.26-8.26-8.26c-4.55,0-8.26,3.71-8.26,8.26c0,4.56,3.71,8.26,8.26,8.26
|
||||
c1.89,0,3.74-0.65,5.2-1.84c1.359-1.11,2.35-2.63,2.79-4.31h-3.891C197.889,158.41,196.3,159.39,194.58,159.39z M206.71,142.86
|
||||
v21.87l-24.38,3.5h-0.26v-25.37H206.71z"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.7 KiB |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="65.333px" height="152.585px" viewBox="0 0 65.333 152.585" enable-background="new 0 0 65.333 152.585"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="5" stroke-dasharray="15.381,10.381" x1="10.221" y1="3.921" x2="10.221" y2="148.108"/>
|
||||
<polyline fill="none" stroke="#FFFFFF" stroke-width="5" points="17.944,139.802 10.221,148.108 2.497,139.802 "/>
|
||||
|
||||
<line fill="none" stroke="#FFFFFF" stroke-width="5" stroke-dasharray="13.311,8.311" x1="7.74" y1="3.921" x2="64.297" y2="3.921"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 901 B |
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 477.175 477.175" style="enable-background:new 0 0 477.175 477.175;" xml:space="preserve" width="512px" height="512px">
|
||||
<g>
|
||||
<path d="M360.731,229.075l-225.1-225.1c-5.3-5.3-13.8-5.3-19.1,0s-5.3,13.8,0,19.1l215.5,215.5l-215.5,215.5 c-5.3,5.3-5.3,13.8,0,19.1c2.6,2.6,6.1,4,9.5,4c3.4,0,6.9-1.3,9.5-4l225.1-225.1C365.931,242.875,365.931,234.275,360.731,229.075z " fill="#FFFFFF"/>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 804 B |
@@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="13.54419mm"
|
||||
height="13.950245mm"
|
||||
viewBox="0 0 13.544191 13.950245"
|
||||
version="1.1"
|
||||
id="svg1050"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)"
|
||||
sodipodi:docname="e-declicIcon.svg">
|
||||
<defs
|
||||
id="defs1044" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="7.9195959"
|
||||
inkscape:cx="60.399667"
|
||||
inkscape:cy="7.6386097"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="1912"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata1047">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-0.82788564,-0.72169744)">
|
||||
<path
|
||||
d="m 105.0772,92.130958 c -2.50437,0 -4.5413,2.037292 -4.5413,4.542367 0,2.504369 2.03693,4.540955 4.5413,4.540955 1.03858,0 2.05458,-0.36019 2.85927,-1.01353 0.74859,-0.608189 1.28764,-1.443567 1.53141,-2.369256 h -2.13784 c -0.4325,0.839258 -1.30704,1.377244 -2.25284,1.377244 -1.3977,0 -2.53576,-1.137355 -2.53576,-2.535413 0,-1.399117 1.13806,-2.53612 2.53576,-2.53612 0.81421,0 1.58503,0.397228 2.06199,1.062214 l 0.13194,0.183444 -0.42263,0.06138 -2.85503,0.41028 c -0.35525,0.08784 -0.57397,0.334434 -0.57397,0.818798 0,0.06421 0.002,0.18168 0.005,0.322791 h 6.15209 l 0.0307,-0.0053 c 0.008,-0.108303 0.0117,-0.215547 0.0117,-0.3175 -1e-5,-2.505076 -2.03765,-4.542368 -4.54167,-4.542368"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
|
||||
id="path989"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="M 0.82788564,0.72169744 V 14.671942 h 0.13793 L 14.372076,12.746128 V 0.72169744 Z"
|
||||
style="fill:#110b09;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
|
||||
id="path965"
|
||||
inkscape:connector-curvature="0" />
|
||||
<path
|
||||
d="m 7.4833696,2.203632 c -2.5043695,0 -4.5413083,2.0372916 -4.5413083,4.5423667 0,2.5043693 2.0369388,4.5409553 4.5413083,4.5409553 1.0385777,0 2.0545777,-0.360186 2.8592634,-1.013531 0.748595,-0.6081883 1.287639,-1.4435663 1.531409,-2.369255 H 9.7362083 C 9.3037027,8.7434264 8.4291667,9.2814124 7.4833696,9.2814124 c -1.3977056,0 -2.5357667,-1.1373557 -2.5357667,-2.5354137 0,-1.3991167 1.1380611,-2.5361195 2.5357667,-2.5361195 0.8142111,0 1.5850304,0.3972277 2.0619861,1.0622138 L 9.6772944,5.4555374 9.2546667,5.5169207 6.3996363,5.9272014 C 6.044389,6.0150434 5.8256667,6.2616347 5.8256667,6.7459987 c 0,0.064205 0.00177,0.1816803 0.00494,0.3227916 h 6.1520913 l 0.03069,-0.00529 c 0.0078,-0.108303 0.01164,-0.2155473 0.01164,-0.3175 C 12.025026,4.2409236 9.987382,2.203632 7.4833656,2.203632"
|
||||
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.35277775"
|
||||
id="path989-8"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="295px" height="178px" viewBox="0 0 295 178" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>Group</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="next-black" transform="translate(-123.000000, -136.000000)" fill="#000000">
|
||||
<g id="Group" transform="translate(123.000000, 135.000000)">
|
||||
<g id="EXT-+-Type-something">
|
||||
<path d="M69.7485407,47.7290323 L125.269767,47.7290323 L125.269767,52.1419355 L74.8379864,52.1419355 L74.8379864,85.3548387 L122.262367,85.3548387 L122.262367,89.7677419 L74.8379864,89.7677419 L74.8379864,126.232258 L125.848113,126.232258 L125.848113,130.645161 L69.7485407,130.645161 L69.7485407,47.7290323 Z M130.243544,47.7290323 L136.142674,47.7290323 L162.283918,84.1935484 L189.003508,47.7290323 L225.346507,1.41071165 L165.638325,88.0258065 L196.406338,130.645161 L190.27587,130.645161 L162.283918,91.8580645 L134.176297,130.645161 L128.161498,130.645161 L159.160849,88.0258065 L130.243544,47.7290323 Z M198.604054,52.1419354 L198.604054,47.7290323 L261.875118,47.7290323 L261.875118,52.1419355 L232.726474,52.1419355 L232.726474,130.645161 L227.637028,130.645161 L227.637028,52.1419355 L198.604054,52.1419354 Z" id="EXT"></path>
|
||||
<polygon id="Type-something" points="0.289190704 47.7290323 6.6509979 47.7290323 94.3765964 179.00202 58.1238016 130.645161 5.6099749 54 5.37863646 130.645161 0.289190704 130.645161"></polygon>
|
||||
</g>
|
||||
<path d="M261.362336,124.900538 C262.414525,124.900538 263.182004,124.094874 263.182004,123.053709 C263.182004,122.012543 262.414525,121.206879 261.362336,121.206879 C260.322526,121.206879 259.542668,122.012543 259.542668,123.053709 C259.542668,124.094874 260.322526,124.900538 261.362336,124.900538 Z M266.363328,120.041764 C266.363328,123.115683 268.579114,125.123645 271.809953,125.123645 C275.251229,125.123645 277.330849,123.053709 277.330849,119.459207 L277.330849,106.804084 L274.558022,106.804084 L274.558022,119.446812 C274.558022,121.44238 273.555348,122.508336 271.785195,122.508336 C270.200723,122.508336 269.123776,121.516749 269.08664,120.041764 L266.363328,120.041764 Z M280.957806,119.880632 C281.155866,123.078498 283.817284,125.123645 287.778466,125.123645 C292.011979,125.123645 294.661019,122.97934 294.661019,119.558366 C294.661019,116.868687 293.150819,115.381308 289.486726,114.526064 L287.518514,114.042666 C285.191319,113.497293 284.250539,112.765998 284.250539,111.489331 C284.250539,109.878002 285.711224,108.824442 287.902253,108.824442 C289.981873,108.824442 291.417802,109.853213 291.677754,111.501725 L294.376309,111.501725 C294.215386,108.489781 291.566346,106.370265 287.939389,106.370265 C284.040101,106.370265 281.440575,108.489781 281.440575,111.675253 C281.440575,114.302957 282.91364,115.864706 286.144479,116.620791 L288.446915,117.178558 C290.811246,117.736326 291.851056,118.554385 291.851056,119.917816 C291.851056,121.504355 290.217069,122.657074 287.988904,122.657074 C285.599816,122.657074 283.941071,121.578724 283.705876,119.880632 L280.957806,119.880632 Z" id=".JS"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3618.6 3618.6" width="3618.6" height="3618.6"><style>.st0{fill:#222}.st1{fill:#00d8ff}.st2{fill:none;stroke:#00d8ff;stroke-width:144.7456;stroke-miterlimit:10}</style><path class="st0" d="M0 0h3618.6v3618.6H0z"/><circle class="st1" cx="1806.5" cy="1807.1" r="302.6"/><path class="st2" d="M1806.5 1191.9c406.2 0 783.6 58.3 1068.1 156.2 342.8 118 553.6 296.9 553.6 458.9 0 168.8-223.4 358.9-591.5 480.8-278.3 92.2-644.6 140.4-1030.2 140.4-395.4 0-769.7-45.2-1051.2-141.4-356.1-121.7-570.6-314.2-570.6-479.8 0-160.7 201.3-338.2 539.3-456 285.6-99.5 672.3-159.1 1082.5-159.1z"/><path class="st2" d="M1271 1501.3c202.9-351.9 442-649.7 669-847.2 273.5-238 533.8-331.2 674.1-250.3 146.2 84.3 199.3 372.8 121 752.7-59.2 287.2-200.4 628.5-393.1 962.6-197.5 342.5-423.7 644.2-647.6 840-283.3 247.7-557.3 337.3-700.7 254.6-139.2-80.3-192.4-343.3-125.7-695 56.4-297.4 198-662.1 403-1017.4z"/><path class="st2" d="M1271.5 2119.8c-203.5-351.6-342.1-707.4-399.9-1002.7-69.6-355.8-20.4-627.9 119.8-709 146.1-84.6 422.5 13.5 712.5 271 219.3 194.7 444.4 487.5 637.6 821.3 198.1 342.2 346.6 688.8 404.3 980.5 73.1 369.2 13.9 651.3-129.4 734.2-139.1 80.5-393.5-4.7-664.9-238.2-229.2-197.3-474.5-502.1-680-857.1z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="2500" height="2031" viewBox="0 0 256 208" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><path d="M15.624 35.012v87.53l15.058-8.471V43.482l-15.058-8.47z" fill="#949494"/><path d="M.565 114.07V8.66l89.411 50.823v17.883L15.624 35.012v87.53L.564 114.07z" fill="#EFEFEF"/><path d="M181.27 8.659V114.07l-60.235 33.882-15.059-9.412 60.236-33.882V34.07L89.976 77.365V59.482L181.271 8.66z" fill="#949494"/><path d="M166.212.188L90.918 42.541 15.624.188.564 8.658l89.412 50.824L181.271 8.66 166.21.189z" fill="#D6D6D6"/><path d="M151.153 42.541v53.647l15.059 8.47V34.072l-15.06 8.47z" fill="#EFEFEF"/><path d="M75.859 138.541l75.294-42.353 15.059 8.47-60.236 33.883 59.295 33.883 60.235-33.883 15.059 8.47-75.294 42.354-89.412-50.824z" fill="#D6D6D6"/><path d="M75.859 138.541v17.883l89.412 50.823v-17.882L75.859 138.54zM240.565 147.012V94.306l-15.06-8.47v52.705l15.06 8.47z" fill="#EFEFEF"/><path d="M165.27 189.365l75.295-42.353V94.306l15.059-8.47v70.588l-90.353 50.823v-17.882z" fill="#949494"/><path d="M255.624 85.835l-15.06 8.47-15.058-8.47 15.059-8.47 15.059 8.47z" fill="#D6D6D6"/><path d="M240.565 77.365V59.482l15.059-8.47v17.882l-15.06 8.47z" fill="#949494"/><path d="M240.565 59.482v17.883l-15.06-8.47V51.011l15.06 8.47z" fill="#EFEFEF"/><path d="M255.624 51.012l-15.06 8.47-15.058-8.47 15.059-8.47 15.059 8.47z" fill="#D6D6D6"/></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,64 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
class="c0112 c017"
|
||||
viewBox="0 0 120 120"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="5-jss.svg"
|
||||
inkscape:version="0.92.3 (2405546, 2018-03-11)">
|
||||
<metadata
|
||||
id="metadata14">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs12" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="834"
|
||||
inkscape:window-height="877"
|
||||
id="namedview10"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.9666667"
|
||||
inkscape:cx="38.119191"
|
||||
inkscape:cy="60.000001"
|
||||
inkscape:window-x="1033"
|
||||
inkscape:window-y="43"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg8" />
|
||||
<path
|
||||
fill="transparent"
|
||||
d="M0 13h95v94H0z"
|
||||
id="path2"
|
||||
style="fill:#f7df1e;fill-opacity:1" />
|
||||
<path
|
||||
fill="#eee"
|
||||
d="M96,107.5H0v-95h96V107.5z M1.803,105.705h92.393v-91.41H1.803C1.803,14.295,1.803,105.705,1.803,105.705z"
|
||||
id="path4"
|
||||
style="fill:#f7df1e;fill-opacity:1" />
|
||||
<path
|
||||
fill="#fff"
|
||||
d="M64.294 86.574c1.903 3.108 4.379 5.392 8.759 5.392 3.679 0 6.029-1.839 6.029-4.379 0-3.044-2.414-4.123-6.464-5.894l-2.219-.952c-6.407-2.729-10.663-6.149-10.663-13.378 0-6.659 5.073-11.728 13.003-11.728 5.645 0 9.704 1.965 12.628 7.109l-6.914 4.439c-1.522-2.73-3.164-3.805-5.714-3.805-2.601 0-4.249 1.65-4.249 3.805 0 2.663 1.65 3.742 5.459 5.392l2.22.951c7.544 3.235 11.803 6.533 11.803 13.948 0 7.993-6.279 12.373-14.713 12.373-8.246 0-13.573-3.929-16.18-9.079 0-.002 7.215-4.194 7.215-4.194zm32.029 0c1.903 3.108 4.379 5.392 8.759 5.392 3.679 0 6.029-1.839 6.029-4.379 0-3.044-2.414-4.123-6.464-5.894l-2.219-.952c-6.407-2.729-10.663-6.149-10.663-13.378 0-6.659 5.073-11.728 13.003-11.728 5.645 0 9.704 1.965 12.628 7.109l-6.914 4.439c-1.522-2.73-3.164-3.805-5.714-3.805-2.601 0-4.249 1.65-4.249 3.805 0 2.663 1.65 3.742 5.459 5.392l2.22.951C115.741 76.76 120 80.058 120 87.473c0 7.993-6.279 12.373-14.713 12.373-8.246 0-13.573-3.929-16.18-9.079l7.216-4.193zm-63.393.77c1.395 2.475 2.664 4.567 5.714 4.567 2.917 0 4.757-1.141 4.757-5.579V56.141h8.878v30.31c0 9.193-5.39 13.378-13.258 13.378-7.109 0-11.226-3.679-13.32-8.11l7.229-4.375c0-.001 0 0 0 0z"
|
||||
id="path6"
|
||||
style="fill:#000000" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="110px" height="100px" viewBox="0 0 110 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 51.2 (57519) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>now-black-logotype</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Black-Now-Logo" transform="translate(-295.000000, -150.000000)" fill="#000000" fill-rule="nonzero">
|
||||
<g id="now-black-logotype" transform="translate(295.000000, 150.000000)">
|
||||
<path d="M55,0.344827586 L0.34375,99.3965517 L109.570312,99.3965517 L55,0.344827586 Z M50.359375,20.3448276 L90.1484375,93.7068966 L9.96875,93.7068966 L50.359375,20.3448276 Z" id="Shape"></path>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
width="2000"
|
||||
height="2008.2758"
|
||||
id="svg2"
|
||||
inkscape:version="0.48.1 r9760"
|
||||
sodipodi:docname="twitter_bootstrap_logo.svg">
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="864"
|
||||
inkscape:window-height="623"
|
||||
id="namedview2996"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="0.24896372"
|
||||
inkscape:cx="116.80866"
|
||||
inkscape:cy="407.66551"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg2" />
|
||||
<defs
|
||||
id="defs4">
|
||||
<linearGradient
|
||||
id="linearGradient3820">
|
||||
<stop
|
||||
id="stop3822"
|
||||
style="stop-color:#070135;stop-opacity:1"
|
||||
offset="0" />
|
||||
<stop
|
||||
id="stop3824"
|
||||
style="stop-color:#612d50;stop-opacity:1"
|
||||
offset="1" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
x1="314.62408"
|
||||
y1="547.20599"
|
||||
x2="427.37671"
|
||||
y2="432.48389"
|
||||
id="linearGradient3834"
|
||||
xlink:href="#linearGradient3820"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(-52,0)" />
|
||||
</defs>
|
||||
<metadata
|
||||
id="metadata7">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="layer1"
|
||||
transform="matrix(15.623041,0,0,15.623041,-3987.6024,-6644.8835)">
|
||||
<rect
|
||||
width="128.01605"
|
||||
height="128.54576"
|
||||
ry="20.679518"
|
||||
x="255.23856"
|
||||
y="425.32587"
|
||||
id="rect3050"
|
||||
style="fill:url(#linearGradient3834);fill-opacity:1;stroke:none" />
|
||||
<g
|
||||
transform="scale(1.0052013,0.99482561)"
|
||||
id="text3836"
|
||||
style="font-size:109.79650116px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:center;line-height:100%;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:middle;fill:#ffffff;fill-opacity:1;stroke:none;font-family:Sans;-inkscape-font-specification:Sans">
|
||||
<path
|
||||
d="m 318.55641,483.16526 c 3.25239,5e-5 5.71852,-0.71477 7.3984,-2.14446 1.67978,-1.42959 2.51969,-3.53831 2.51974,-6.32617 -5e-5,-2.752 -0.83996,-4.84285 -2.51974,-6.27255 -1.67988,-1.46532 -4.14601,-2.19801 -7.3984,-2.19808 l -11.41926,0 0,16.94126 11.41926,0 m 0.69695,35.00836 c 4.14592,10e-6 7.25538,-0.87564 9.32841,-2.62697 2.10867,-1.75129 3.16303,-4.39613 3.16309,-7.93451 -6e-5,-3.46686 -1.03655,-6.05808 -3.10947,-7.77368 -2.07303,-1.75128 -5.20037,-2.62693 -9.38203,-2.62697 l -12.11621,0 0,20.96213 12.11621,0 M 338.4463,489.3842 c 4.43183,1.28672 7.86296,3.6635 10.29343,7.13034 2.43031,3.46692 3.64551,7.7201 3.64558,12.75956 -7e-5,7.72008 -2.60917,13.47438 -7.82729,17.26292 -5.21825,3.78855 -13.15276,5.68283 -23.80353,5.68283 l -34.2578,0 0,-80.04208 30.98749,0 c 11.11541,8e-5 19.15714,1.67991 24.12521,5.03949 5.00367,3.35973 7.50554,8.73875 7.50562,16.13708 -8e-5,3.89583 -0.91147,7.21975 -2.73419,9.97175 -1.82286,2.71637 -4.4677,4.73574 -7.93452,6.05811"
|
||||
id="path3841"
|
||||
style="font-weight:bold;line-height:100%;fill:#ffffff;font-family:Sans;-inkscape-font-specification:Sans Bold"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2500" height="2500" viewBox="0 0 538.584 538.583"><path d="M0 0h538.584v538.583H0V0z" fill="none"/><path d="M405.5 467.454L269.29 504.13l-136.212-36.676-31.432-340.525h335.29L405.5 467.454z" fill="#2062af"/><path d="M269.289 154.511v320.367l.308.084 110.229-29.682 25.443-290.769h-135.98z" fill="#3c9cd7"/><path d="M191.985 76.899V56.631h29.571V34.453h-51.749v62.684h51.749V76.899h-29.571zm82.766-20.268h20.674V34.453h-51.754v22.178c6.924 6.924 10.535 10.238 20.549 20.252-5.854 0-20.549.021-20.549.02v20.234h51.754V76.899l-20.674-20.268zm73.352 0h20.673V34.453H317.02v22.178c6.924 6.924 10.537 10.238 20.551 20.252-5.852 0-20.551.021-20.551.02v20.234h51.757V76.899l-20.674-20.268z"/><path d="M269.168 239.656l-97.49 40.602 3.233 40.199 94.257-40.301 100.265-42.868 4.157-41.122-104.422 43.49z" fill="#fff"/><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="-825.508" y1="1338.301" x2="-825.508" y2="1419.102" gradientTransform="matrix(1 0 0 -1 1045.93 1658.759)"><stop offset=".387" stop-color="#d1d3d4" stop-opacity="0"/><stop offset="1" stop-color="#d1d3d4"/></linearGradient><path d="M171.677 280.258l3.233 40.199 94.257-40.301v-40.5l-97.49 40.602z" fill="url(#a)"/><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="-724.552" y1="1378.602" x2="-724.552" y2="1462.591" gradientTransform="matrix(1 0 0 -1 1045.93 1658.759)"><stop offset=".387" stop-color="#d1d3d4" stop-opacity="0"/><stop offset="1" stop-color="#d1d3d4"/></linearGradient><path d="M373.59 196.167l-104.422 43.489v40.5l100.265-42.868 4.157-41.121z" fill="url(#b)"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="-874.103" y1="1302.263" x2="-680.039" y2="1302.263" gradientTransform="matrix(1 0 0 -1 1045.93 1658.759)"><stop offset="0" stop-color="#e8e7e5"/><stop offset="1" stop-color="#fff"/></linearGradient><path d="M171.827 280.258l3.234 40.199 144.625.461-3.235 53.598-47.59 13.398-45.748-11.551-2.772-33.268h-42.508l5.545 64.225 85.945 25.412 85.479-24.951 11.09-127.523H171.827z" fill="url(#c)"/><path d="M269.168 280.258h-97.49l3.233 40.199 94.257.301v-40.5zm0 107.528l-.462.129-45.742-11.551-2.772-33.268h-42.507l5.544 64.225 85.939 25.412v-44.947z" opacity=".05"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="-883.032" y1="1442.031" x2="-672.341" y2="1442.031" gradientTransform="matrix(1 0 0 -1 1045.93 1658.759)"><stop offset="0" stop-color="#e8e7e5"/><stop offset="1" stop-color="#fff"/></linearGradient><path d="M162.898 196.167H373.59l-4.157 41.122H167.98l-5.082-41.122z" fill="url(#d)"/><path d="M269.168 196.167h-106.27l5.082 41.122h101.188v-41.122z" opacity=".05"/></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,45 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.1"
|
||||
width="260"
|
||||
height="90"
|
||||
id="svg2948">
|
||||
<defs
|
||||
id="defs2950" />
|
||||
<g
|
||||
transform="translate(121.06306,-355.93976)"
|
||||
id="layer1">
|
||||
<path
|
||||
d="m -101.47577,400.00486 c -0.0431,8.1975 5.967822,13.08438 15.560005,10.8625 l 0,-21.725 c -9.713433,-2.95812 -15.519375,3.02656 -15.560005,10.8625 m 15.853442,-42.27625 13.21125,0 0,62.53344 c -12.663437,2.07031 -24.79187,3.38344 -33.468442,-1.46813 -11.17187,-6.24625 -12.07,-26.09562 -3.22968,-34.93625 4.48218,-4.48187 13.505622,-7.63718 23.193435,-5.57812 l 0,-19.67 c -0.02531,-0.41688 -0.04875,-0.83188 0.293437,-0.88094"
|
||||
id="path2838"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
<path
|
||||
d="m -50.979419,371.52699 -13.211251,0 0,-13.79844 13.211251,0 0,13.79844 z"
|
||||
id="path2840"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
<path
|
||||
d="m 107.55577,411.45442 c 14.40218,5.59688 15.99562,-25.77875 1.46812,-22.89968 -9.02187,1.78812 -8.73218,20.07687 -1.46812,22.89968 m 0.58718,-33.76218 c 10.99844,-1.17594 19.29594,3.6775 22.60625,11.15625 5.64813,12.76031 -0.31062,27.40218 -9.39468,31.70718 -4.70813,2.23125 -9.63813,2.44375 -14.09219,2.055 -13.766874,-1.20062 -22.449374,-15.23062 -17.614999,-30.82656 0.756562,-2.44094 1.606562,-3.93406 2.055312,-4.69719 2.9925,-5.08812 8.044688,-8.49687 16.440307,-9.39468"
|
||||
id="path2844"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
<path
|
||||
d="m -32.777012,406.46358 c -3.654062,6.14094 5.526875,7.61219 11.449375,5.87187 l 0,-9.10125 c -4.550625,0.63532 -9.730312,0.33969 -11.449375,3.22938 m 23.192813,14.38562 c -10.913125,0.62782 -27.498125,4.34532 -33.468438,-2.93562 -3.5075,-4.27813 -3.130312,-12.43781 0,-16.44094 4.045313,-5.1725 12.875938,-6.30062 20.844375,-7.04594 0.723438,-1.78125 0.235625,-4.67937 -0.587187,-5.87218 -7.28875,-1.85657 -14.530625,1.02031 -19.669688,3.22937 l 0,-10.275 c 7.761875,-3.56625 25.740625,-7.32125 31.119688,0.88063 2.825,4.30781 1.76125,11.82062 1.76125,18.49562 l 0,19.96406 z"
|
||||
id="path2848"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
<path
|
||||
d="m 36.802301,421.43674 -13.21125,0 0,-17.02813 c 0,-5.60656 0.906875,-12.71562 -2.055625,-14.97281 -2.32,-1.76875 -7.824063,-0.88125 -11.155625,0 l 0,32.00094 -13.2115628,0 0,-40.80844 c 13.5053128,-3.50813 37.8831248,-7.26938 39.6340628,9.68812 0.863437,8.36063 -0.639688,20.90407 0,31.12032"
|
||||
id="path2852"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
<path
|
||||
d="m 56.472393,407.34433 c 2.223125,3.59687 8.243125,4.94562 13.210938,2.64219 -0.272813,-6.87094 -0.368125,-13.91938 -0.293438,-21.13813 -11.4175,-3.01656 -18.221875,9.91594 -12.9175,18.49594 m 33.175,-27.89031 c -1.175937,3.52156 -2.896875,6.4975 -4.40375,9.68843 -1.361562,0.305 -2.541562,-0.54125 -3.229687,0 -0.30125,14.31094 3.047812,33.71157 -2.642188,44.03719 -3.425937,6.21688 -9.733437,10.20531 -19.376562,10.86281 -7.024688,0.47907 -12.476875,-0.66875 -17.615,-2.64218 l 0,-12.33063 c 6.026562,3.51875 21.255,6.3875 25.541875,-0.88094 1.26125,-2.13843 1.18125,-4.34875 1.76125,-7.63312 -2.76,0.145 -5.439375,1.42312 -8.807188,1.46812 -13.845937,0.18375 -22.411875,-10.84093 -19.083125,-26.12937 1.2875,-5.91375 4.7525,-10.67531 9.395,-13.50469 9.641875,-5.87656 24.462813,-5.43031 38.459375,-2.93562"
|
||||
id="path2856"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
<path
|
||||
d="m -50.979389,378.51405 c -0.015,9.72844 -0.0128,16.71906 -0.01375,28.53781 -0.56125,11.61469 -0.430625,20.86375 -4.683438,26.42125 -0.83875,1.09594 -2.62,2.69469 -4.110312,3.81688 -1.251564,0.94218 -3.636876,2.85281 -4.990939,2.93593 -1.365,0.0838 -4.871875,-1.895 -6.459062,-2.6425 -2.084063,-0.98156 -4.010625,-2.285 -6.165,-2.93562 5.025312,-3.11406 9.795625,-5.44875 11.743125,-11.74344 1.69375,-5.47437 1.468125,-13.40719 1.468125,-21.13812 0,-7.63375 0.03406,-16.50875 0.03406,-23.24875 l 13.177189,-0.003 z"
|
||||
id="path2860"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 23 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="2500" height="2500" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMinYMin meet"><path d="M251.172 116.594L139.4 4.828c-6.433-6.437-16.873-6.437-23.314 0l-23.21 23.21 29.443 29.443c6.842-2.312 14.688-.761 20.142 4.693 5.48 5.489 7.02 13.402 4.652 20.266l28.375 28.376c6.865-2.365 14.786-.835 20.269 4.657 7.663 7.66 7.663 20.075 0 27.74-7.665 7.666-20.08 7.666-27.749 0-5.764-5.77-7.188-14.235-4.27-21.336l-26.462-26.462-.003 69.637a19.82 19.82 0 0 1 5.188 3.71c7.663 7.66 7.663 20.076 0 27.747-7.665 7.662-20.086 7.662-27.74 0-7.663-7.671-7.663-20.086 0-27.746a19.654 19.654 0 0 1 6.421-4.281V94.196a19.378 19.378 0 0 1-6.421-4.281c-5.806-5.798-7.202-14.317-4.227-21.446L81.47 39.442l-76.64 76.635c-6.44 6.443-6.44 16.884 0 23.322l111.774 111.768c6.435 6.438 16.873 6.438 23.316 0l111.251-111.249c6.438-6.44 6.438-16.887 0-23.324" fill="#DE4C36"/></svg>
|
||||
|
After Width: | Height: | Size: 898 B |