Compare commits

..

6 Commits

Author SHA1 Message Date
Thomas Berrod 6dbfad4d7d doc 2019-05-20 17:29:19 +02:00
Thomas Berrod db10c3d820 Changement de dossier 2019-05-20 17:27:00 +02:00
Thomas Berrod 5f9b40e9f7 Reprise du projet 2019-05-20 17:03:49 +02:00
Thomas Berrod 516cdf176b linkedin bugfix 2018-09-28 17:04:23 +02:00
Thomas Berrod 3235543cd7 auth a bit stabilized 2018-09-28 16:45:45 +02:00
Thomas Berrod f7f8b98804 next-auth unstable 2018-09-28 16:20:26 +02:00
34527 changed files with 2922037 additions and 99608 deletions
-15
View File
@@ -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}"
}
]
}
-3
View File
@@ -1,3 +0,0 @@
{
"git.ignoreLimitWarning": true
}
+19
View File
@@ -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
+6
View File
@@ -0,0 +1,6 @@
/node_modules/
/.next/
/out/
TODO
/cypress/*
!/cypress/integration
+22
View File
@@ -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}"
}
]
}
+6
View File
@@ -0,0 +1,6 @@
# pwa-boilerplate
Startup React.js project with docker
with next.js 7
+82
View File
@@ -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)
+245
View File
@@ -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)
+180
View File
@@ -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)
File diff suppressed because one or more lines are too long
+115
View File
@@ -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);
+23
View File
@@ -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);
+164
View File
@@ -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)
+80
View File
@@ -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>
);
}
}
+137
View File
@@ -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
+121
View File
@@ -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);
+68
View File
@@ -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);
+140
View File
@@ -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);
+111
View File
@@ -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>
)
}
}
+105
View File
@@ -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)
+29
View File
@@ -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"
+1
View File
@@ -0,0 +1 @@
{}
+34
View File
@@ -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')
});
});
+21
View File
@@ -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"
);
+16
View File
@@ -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'
}
}
]
}
+85
View File
@@ -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']}
})
})
})
}
+271
View File
@@ -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
}
*/
})
})
}
+131
View File
@@ -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
}
+107
View File
@@ -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'
}
},
);
+76
View File
@@ -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"
}
}
+94
View File
@@ -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;
+122
View File
@@ -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;
+72
View File
@@ -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))
+269
View File
@@ -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>
)
}
}
}
+57
View File
@@ -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>
)
}
}
+40
View File
@@ -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>
)
}
}
+103
View File
@@ -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>
)
}
}
}
+68
View File
@@ -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>
)
}
}
}
+59
View File
@@ -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>
)
}
}
}
+295
View File
@@ -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)
+42
View File
@@ -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)
+279
View File
@@ -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)
+362
View File
@@ -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);
+84
View File
@@ -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'})
}
})
}
+87
View File
@@ -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)
})
})
}
+302
View File
@@ -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)
})
+12
View File
@@ -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"
}
+36
View File
@@ -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;
+29
View File
@@ -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);
}
+79
View File
@@ -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__;
}
+316
View File
@@ -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;
}
}());
+40
View File
@@ -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);
}
+29
View File
@@ -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
+76
View File
@@ -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;
}
+131
View File
@@ -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;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 KiB

Binary file not shown.

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+85
View File
@@ -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

+14
View File
@@ -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

+37
View File
@@ -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

+18
View File
@@ -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

+64
View File
@@ -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

File diff suppressed because one or more lines are too long

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

File diff suppressed because one or more lines are too long

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

+1
View File
@@ -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

+45
View File
@@ -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

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 23 KiB

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