stall
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
import React from 'react';
|
||||
import PropTypes from "prop-types";
|
||||
import Router from 'next/router'
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Card from '@material-ui/core/Card';
|
||||
import CardContent from '@material-ui/core/CardContent';
|
||||
import CardHeader from '@material-ui/core/CardHeader';
|
||||
import CardMedia from '@material-ui/core/CardMedia';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
|
||||
|
||||
const styles = theme => ({
|
||||
card: {
|
||||
display: 'flex',
|
||||
},
|
||||
details: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
},
|
||||
content: {
|
||||
flex: '1 0 auto',
|
||||
},
|
||||
cover: {
|
||||
width: 151,
|
||||
height: 151,
|
||||
},
|
||||
});
|
||||
|
||||
class CompanyRowComponent extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, theme, rowData } = this.props;
|
||||
|
||||
const imgDefaultUrl = "https://www.talentstube.com/img/default_picture_company.svg"
|
||||
|
||||
const imgUrl = (rowData.presentation_video && rowData.presentation_video.thumbnails[0] && rowData.presentation_video.thumbnails[0].sizes[4]) ? rowData.presentation_video.thumbnails[0].sizes[4].link : imgDefaultUrl
|
||||
|
||||
|
||||
return (
|
||||
<Card onClick={() => Router.push(`/Company?id=${rowData.id}`)}>
|
||||
<CardHeader />
|
||||
<CardContent>
|
||||
<CardMedia
|
||||
className={classes.cover}
|
||||
image={imgUrl}
|
||||
title={rowData.presentation_video&&rowData.presentation_video.title}
|
||||
/>
|
||||
<Typography gutterBottom variant="headline" component="h3"
|
||||
className={classes.details}>
|
||||
{rowData.brand_name}
|
||||
</Typography>
|
||||
<Typography gutterBottom variant="headline" component="h4"
|
||||
className={classes.details}>
|
||||
{rowData.website}
|
||||
</Typography>
|
||||
<Typography gutterBottom variant="headline" component="h5"
|
||||
className={classes.details}>
|
||||
{rowData.description}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
CompanyRowComponent.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
theme: PropTypes.object.isRequired,
|
||||
rowData: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
export default withStyles(styles, { withTheme: true })(CompanyRowComponent);
|
||||
@@ -0,0 +1,20 @@
|
||||
import Link from 'next/link'
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import BottomToolbar from './BottomToolbar';
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
height: '10vh'
|
||||
},
|
||||
});
|
||||
|
||||
function Footer(props) {
|
||||
const { classes } = props;
|
||||
return (
|
||||
<BottomToolbar/>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export default withStyles(styles)(Footer)
|
||||
@@ -0,0 +1,26 @@
|
||||
import Link from 'next/link'
|
||||
import PropTypes from "prop-types";
|
||||
import { withStyles } from '@material-ui/core/styles'
|
||||
import PSAB from './PrimarySearchAppBar'
|
||||
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
height: '10vh'
|
||||
},
|
||||
});
|
||||
|
||||
function Header(props) {
|
||||
return (
|
||||
<PSAB backLink={props.backLink}/>
|
||||
);
|
||||
|
||||
}
|
||||
Header.defaultProps = {
|
||||
backLink : ""
|
||||
}
|
||||
Header.propTypes = {
|
||||
backLink : PropTypes.string
|
||||
}
|
||||
export default withStyles(styles)(Header)
|
||||
@@ -0,0 +1,208 @@
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
import Head from 'next/head'
|
||||
import Link from 'next/link'
|
||||
import { Container, Row, Col, Nav, NavItem, Button, Form, NavLink, Collapse,
|
||||
Navbar, NavbarToggler, NavbarBrand, Modal, ModalHeader, ModalBody,
|
||||
ModalFooter, ListGroup, ListGroupItem } from 'reactstrap'
|
||||
import Signin from './signin'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
import Cookies from 'universal-cookie'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
static propTypes() {
|
||||
return {
|
||||
session: React.PropTypes.object.isRequired,
|
||||
providers: React.PropTypes.object.isRequired,
|
||||
children: React.PropTypes.object.isRequired,
|
||||
fluid: React.PropTypes.boolean,
|
||||
navmenu: React.PropTypes.boolean,
|
||||
signinBtn: React.PropTypes.boolean
|
||||
}
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
navOpen: false,
|
||||
modal: false,
|
||||
providers: null
|
||||
}
|
||||
this.toggleModal = this.toggleModal.bind(this)
|
||||
}
|
||||
|
||||
async toggleModal(e) {
|
||||
if (e) e.preventDefault()
|
||||
|
||||
// Save current URL so user is redirected back here after signing in
|
||||
if (this.state.modal !== true) {
|
||||
const cookies = new Cookies()
|
||||
cookies.set('redirect_url', window.location.pathname, { path: '/' })
|
||||
}
|
||||
|
||||
this.setState({
|
||||
providers: this.state.providers || await NextAuth.providers(),
|
||||
modal: !this.state.modal
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
|
||||
<MainBody navmenu={this.props.navmenu} fluid={this.props.fluid} container={this.props.container}>
|
||||
{this.props.children}
|
||||
</MainBody>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class MainBody extends React.Component {
|
||||
render() {
|
||||
if (this.props.container === false) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{this.props.children}
|
||||
</React.Fragment>
|
||||
)
|
||||
} else if (this.props.navmenu === false) {
|
||||
return (
|
||||
<Container fluid={this.props.fluid} style={{marginTop: '1em'}}>
|
||||
{this.props.children}
|
||||
</Container>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<Container fluid={this.props.fluid} style={{marginTop: '1em'}}>
|
||||
<Row>
|
||||
<Col xs="12" md="9" lg="10">
|
||||
{this.props.children}
|
||||
</Col>
|
||||
<Col xs="12" md="3" lg="2" style={{paddingTop: '1em'}}>
|
||||
<h5 className="text-muted text-uppercase">Examples</h5>
|
||||
<ListGroup>
|
||||
<ListGroupItem>
|
||||
<Link prefetch href="/examples/authentication"><a href="/examples/authentication" className="d-block">Auth</a></Link>
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
<Link prefetch href="/examples/async"><a href="/examples/async" className="d-block">Async</a></Link>
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
<Link prefetch href="/examples/layout"><a href="/examples/layout" className="d-block">Layout</a></Link>
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
<Link prefetch href="/examples/routing"><a href="/examples/routing" className="d-block">Routing</a></Link>
|
||||
</ListGroupItem>
|
||||
<ListGroupItem>
|
||||
<Link prefetch href="/examples/styling"><a href="/examples/styling" className="d-block">Styling</a></Link>
|
||||
</ListGroupItem>
|
||||
</ListGroup>
|
||||
</Col>
|
||||
</Row>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class UserMenu extends React.Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.handleSignoutSubmit = this.handleSignoutSubmit.bind(this)
|
||||
}
|
||||
|
||||
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.props.session && this.props.session.user) {
|
||||
// If signed in display user dropdown menu
|
||||
const session = this.props.session
|
||||
return (
|
||||
<Nav className="ml-auto" navbar>
|
||||
{/*<!-- Uses .nojs-dropdown CSS to for a dropdown that works without client side JavaScript ->*/}
|
||||
<div tabIndex="2" className="dropdown nojs-dropdown">
|
||||
<div className="nav-item">
|
||||
<span className="dropdown-toggle nav-link d-none d-md-block">
|
||||
<span className="icon ion-md-contact" style={{fontSize: '2em', position: 'absolute', top: -5, left: -25}}></span>
|
||||
</span>
|
||||
<span className="dropdown-toggle nav-link d-block d-md-none">
|
||||
<span className="icon ion-md-contact mr-2"></span>
|
||||
{session.user.name || session.user.email}
|
||||
</span>
|
||||
</div>
|
||||
<div className="dropdown-menu">
|
||||
<Link prefetch href="/account">
|
||||
<a href="/account" className="dropdown-item"><span className="icon ion-md-person mr-1"></span> Your Account</a>
|
||||
</Link>
|
||||
<AdminMenuItem {...this.props}/>
|
||||
<div className="dropdown-divider d-none d-md-block"/>
|
||||
<div className="dropdown-item p-0">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Nav>
|
||||
)
|
||||
} if (this.props.signinBtn === false) {
|
||||
// If not signed in, don't display sign in button if disabled
|
||||
return null
|
||||
} else {
|
||||
// If not signed in, display sign in button
|
||||
return (
|
||||
<Nav className="ml-auto" navbar>
|
||||
<NavItem>
|
||||
{/**
|
||||
* @TODO Add support for passing current URL path as redirect URL
|
||||
* so that users without JavaScript are also redirected to the page
|
||||
* they were on before they signed in.
|
||||
**/}
|
||||
<a href="/auth?redirect=/" className="btn btn-outline-primary" onClick={this.props.toggleModal}><span className="icon ion-md-log-in mr-1"></span> Sign up / Sign in</a>
|
||||
</NavItem>
|
||||
</Nav>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class AdminMenuItem extends React.Component {
|
||||
render() {
|
||||
if (this.props.session.user && this.props.session.user.admin === true) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Link prefetch href="/admin">
|
||||
<a href="/admin" className="dropdown-item"><span className="icon ion-md-settings mr-1"></span> Admin</a>
|
||||
</Link>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return(<div/>)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SigninModal extends React.Component {
|
||||
render() {
|
||||
if (this.props.providers === null) return null
|
||||
|
||||
return (
|
||||
<Modal isOpen={this.props.modal} toggle={this.props.toggleModal} style={{maxWidth: 700}}>
|
||||
<ModalHeader>Sign up / Sign in</ModalHeader>
|
||||
<ModalBody style={{padding: '1em 2em'}}>
|
||||
<Signin session={this.props.session} providers={this.props.providers}/>
|
||||
</ModalBody>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
import React from 'react'
|
||||
|
||||
export default class extends React.Component {
|
||||
render() {
|
||||
if (this.props.fullscreen) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<style jsx global>{`
|
||||
.circle-loader {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 50%;
|
||||
z-index: 100;
|
||||
text-align: center;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.circle-loader .circle {
|
||||
fill: transparent;
|
||||
stroke: rgba(0,0,0,0.2);
|
||||
stroke-width: 4px;
|
||||
animation: dash 2s ease infinite, rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1,95;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -25;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -93;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {transform: rotate(0deg); }
|
||||
100% {transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
<span className="circle-loader">
|
||||
<svg className="circle" width="60" height="60" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="30" cy="30" r="15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<style jsx global>{`
|
||||
.circle-loader {
|
||||
display: block;
|
||||
text-center;
|
||||
padding-left: 50%;
|
||||
}
|
||||
|
||||
.circle-loader .circle {
|
||||
position: relative;
|
||||
left: -30px;
|
||||
fill: transparent;
|
||||
stroke: rgba(0,0,0,0.2);
|
||||
stroke-width: 4px;
|
||||
animation: dash 2s ease infinite, rotate 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dash {
|
||||
0% {
|
||||
stroke-dasharray: 1,95;
|
||||
stroke-dashoffset: 0;
|
||||
}
|
||||
50% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -25;
|
||||
}
|
||||
100% {
|
||||
stroke-dasharray: 85,95;
|
||||
stroke-dashoffset: -93;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes rotate {
|
||||
0% {transform: rotate(0deg); }
|
||||
100% {transform: rotate(360deg); }
|
||||
}
|
||||
`}</style>
|
||||
<span className="circle-loader">
|
||||
<svg className="circle" width="60" height="60" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="30" cy="30" r="15"/>
|
||||
</svg>
|
||||
</span>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from 'react'
|
||||
import Layout from './layoutAuth'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
export default class extends React.Component {
|
||||
|
||||
static async getInitialProps({req}) {
|
||||
return {
|
||||
session: await NextAuth.init({req}),// Add this.props.session to all pages
|
||||
lang: 'en'// Add a lang property for accessibility
|
||||
}
|
||||
}
|
||||
|
||||
adminAcccessOnly() {
|
||||
return (
|
||||
<Layout {...this.props} navmenu={false}>
|
||||
<div className="text-center pt-5 pb-5">
|
||||
<h1 className="display-4 mb-5">Access Denied</h1>
|
||||
<p className="lead">You must be signed in as an administrator to access this page.</p>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
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>
|
||||
</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>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -49,5 +49,40 @@ export default {
|
||||
return show.data;
|
||||
}
|
||||
|
||||
},
|
||||
companies:{
|
||||
fetchCompany: async (id) => {
|
||||
const res = await fetch(`http://localhost/app_dev.php/api/companies/${id}`)
|
||||
const company = await res.json()
|
||||
|
||||
return { company: company.data }
|
||||
},
|
||||
fetchCompanies: async (limit = "", order = "", keyword = "", offset = 1) => {
|
||||
var obj = {
|
||||
method: 'GET',
|
||||
//mode : 'no-cors',
|
||||
headers: {
|
||||
// 'Access-Control-Request-Headers': 'Authorization',
|
||||
// 'Authorization': 'Basic amFzcGVyYWRtaW46amFzcGVyYWRtaW4=',
|
||||
'Content-Type': 'application/json',
|
||||
'Origin': ''
|
||||
},
|
||||
credentials: 'include'
|
||||
};
|
||||
|
||||
let url = `http://localhost/app_dev.php/api/companies?offset=${offset}`;
|
||||
if (limit != "") {
|
||||
url = url + `&limit=${limit}`;
|
||||
}
|
||||
if (order != "") {
|
||||
url = url + `&order=${order}`;
|
||||
}
|
||||
if (keyword != "") {
|
||||
url = url + `&keyword=${keyword}`;
|
||||
}
|
||||
const res = await fetch(url, obj);
|
||||
const show = await res.json();
|
||||
return show.data;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* 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)
|
||||
// port: process.env.PORT || 3000,
|
||||
// Secret used to encrypt session data on the server.
|
||||
sessionSecret: 'fkjpeozr',
|
||||
// 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,
|
||||
sessionResave : true
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* 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: process.env.EMAIL_SECURE || true,
|
||||
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
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
// Email Sign In
|
||||
//
|
||||
// Accounts are created automatically, as when signing in via oAuth.
|
||||
// Users are sent one-time use sign in tokens in links. This avoids
|
||||
// storing user supplied passwords anywhere, preventing password re-use.
|
||||
//
|
||||
// To disable this option, do not set sendSignInEmail (or set it to null).
|
||||
sendSignInEmail: ({email, url, req}) => {
|
||||
nodemailer
|
||||
.createTransport(nodemailerTransport)
|
||||
.sendMail({
|
||||
to: email,
|
||||
from: process.env.EMAIL_FROM,
|
||||
subject: 'Sign in link',
|
||||
text: `Use the link below to sign in:\n\n${url}\n\n`,
|
||||
html: `<p>Use the link below to sign in:</p><p>${url}</p>`
|
||||
}, (err) => {
|
||||
if (err) {
|
||||
console.error('Error sending email to ' + email, err)
|
||||
}
|
||||
})
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log('Generated sign in link ' + url + ' for ' + email)
|
||||
}
|
||||
},
|
||||
// Credentials Sign In
|
||||
//
|
||||
// If you use this you will need to define your own way to validate
|
||||
// credentials. Unlike with oAuth or Email Sign In, accounts are not
|
||||
// created automatically so you will need to provide a way to create them.
|
||||
//
|
||||
// This feature is intended for strategies like Two Factor Authentication.
|
||||
//
|
||||
// To disable this option, do not set signin (or set it to null).
|
||||
/*
|
||||
signIn: ({form, req}) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Should validate credentials (e.g. hash password, compare 2FA token
|
||||
// etc) and return a valid user object from a database.
|
||||
return usersCollection.findOne({
|
||||
email: form.email
|
||||
}, (err, user) => {
|
||||
if (err) return reject(err)
|
||||
if (!user) return resolve(null)
|
||||
|
||||
// Check credentials - e.g. compare bcrypt password hashes
|
||||
if (form.password === "test1234") {
|
||||
// If valid, return user object - e.g. { id, name, email }
|
||||
return resolve(user)
|
||||
} else {
|
||||
// If invalid, return null
|
||||
return resolve(null)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
*/
|
||||
// Session Object (optional)
|
||||
//
|
||||
// The session object that gets returned to the client. You don't need to
|
||||
// specify this function here unless you want to override or extend the
|
||||
// default (e.g. with any other properties you have added to req.session)
|
||||
//
|
||||
// Note: The object returned will be stored in localStorage and visible
|
||||
// client side so do not return data you would not want the user to see.
|
||||
/*
|
||||
session: (session, req) => {
|
||||
if (req.session && req.session.someCustomProperty)
|
||||
session.someCustomProperty = req.session.someCustomProperty
|
||||
|
||||
session.someOtherCustomProperty = "Example custom property"
|
||||
|
||||
return session
|
||||
}
|
||||
*/
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return providers
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// next.config.js
|
||||
const withPlugins = require('next-compose-plugins');
|
||||
const optimizedImages = require('next-optimized-images');
|
||||
const webpack = require("webpack");
|
||||
|
||||
module.exports = withPlugins([
|
||||
[optimizedImages, {
|
||||
@@ -8,5 +9,44 @@ module.exports = withPlugins([
|
||||
}],
|
||||
|
||||
// your other plugins here
|
||||
// {
|
||||
// webpack: (config, { isServer }) => {
|
||||
|
||||
// // config.module.noParse = /jpg/;
|
||||
|
||||
// // config.module.rules.push(
|
||||
// // {
|
||||
// // test: /\.js$/,
|
||||
// // loader: 'babel-loader',
|
||||
// // exclude: /node_modules/,
|
||||
|
||||
// // },
|
||||
// // {
|
||||
// // test: /\.css$/,
|
||||
// // loader: 'style!css'
|
||||
// // },
|
||||
// // {
|
||||
// // test: /\.html$/,
|
||||
// // loader: 'raw'
|
||||
// // }
|
||||
// // )
|
||||
|
||||
// // const videojsPlugin = new webpack.ProvidePlugin({
|
||||
// // videojs: "video.js/dist/video.cjs.js",
|
||||
// // RecordRTC: "recordrtc",
|
||||
// // MediaStreamRecorder: ["recordrtc", "MediaStreamRecorder"]
|
||||
// // });
|
||||
// // const videojsAlias = {
|
||||
// // videojs: "video.js",
|
||||
// // WaveSurfer: "wavesurfer.js",
|
||||
// // RecordRTC: "recordrtc"
|
||||
// // };
|
||||
|
||||
|
||||
// // config.resolve.alias = { ...config.resolve.alias, ...videojsAlias };
|
||||
// // config.plugins.push(videojsPlugin);
|
||||
|
||||
// return config
|
||||
// }
|
||||
|
||||
]);
|
||||
+21
-3
@@ -2,29 +2,47 @@
|
||||
"name": "nextjs",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"resolutions": {
|
||||
"terser": "3.14.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@material-ui/core": "latest",
|
||||
"@material-ui/icons": "^3.0.1",
|
||||
"connect-mongo": "^2.0.1",
|
||||
"cross-env": "^5.2.0",
|
||||
"dotenv": "^6.0.0",
|
||||
"eslint": "^5.6.0",
|
||||
"express": "^4.16.3",
|
||||
"griddle-react": "^1.13.1",
|
||||
"isomorphic-unfetch": "^3.0.0",
|
||||
"jss": "latest",
|
||||
"lodash": "^4.17.11",
|
||||
"next": "latest",
|
||||
"mongodb": "^3.1.6",
|
||||
"nedb": "^1.8.0",
|
||||
"next": "8.1.0",
|
||||
"next-auth": "^1.11.0",
|
||||
"next-compose-plugins": "^2.1.1",
|
||||
"next-optimized-images": "^1.4.1",
|
||||
"nodemailer": "^4.6.8",
|
||||
"nodemailer-direct-transport": "^3.3.2",
|
||||
"nodemailer-smtp-transport": "^2.7.4",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-linkedin-oauth2": "^1.5.0",
|
||||
"prop-types": "latest",
|
||||
"react": "latest",
|
||||
"react-dom": "latest",
|
||||
"react-jss": "latest",
|
||||
"react-plyr": "^2.1.1",
|
||||
"video.js": "^7.2.2"
|
||||
"reactstrap": "^6.4.0",
|
||||
"terser": "3.14",
|
||||
"universal-cookie": "^3.0.4",
|
||||
"video.js": "^7.2.2",
|
||||
"videojs-record": "^3.6.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "node server.js",
|
||||
"build": "next build",
|
||||
"start": "NODE_ENV=production node server.js"
|
||||
"start": "cross-env NODE_ENV=production node server.js"
|
||||
},
|
||||
"eslint.packageManager": "yarn"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
|
||||
import Grid from "../components/templates/Grid"
|
||||
import LayoutGrid from "../components/templates/LayoutGrid"
|
||||
import { Typography } from '@material-ui/core';
|
||||
import CompanyRow from "../components/organisms/CompanyRow"
|
||||
|
||||
import api from '../data/api'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
paddingTop: theme.spacing.unit * 20,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
class Companies extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<LayoutGrid backLink="/">
|
||||
<div className={classes.container}>
|
||||
<Grid CustomRowComponent={CompanyRow} dataFetcher={api.companies.fetchCompanies}>
|
||||
</Grid>
|
||||
</div>
|
||||
</LayoutGrid>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Companies.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Companies);
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
import React from 'react';
|
||||
import fetch from 'isomorphic-unfetch'
|
||||
import PropTypes from 'prop-types';
|
||||
import { withStyles } from '@material-ui/core/styles';
|
||||
import Typography from '@material-ui/core/Typography';
|
||||
import Plyr from 'react-plyr';
|
||||
import _ from "lodash/string";
|
||||
import Ar from "lodash/array";
|
||||
|
||||
import api from "../data/api"
|
||||
import LayoutDetail from "../components/templates/LayoutDetail";
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
textAlign: 'center',
|
||||
paddingTop: theme.spacing.unit * 20,
|
||||
justifyContent: 'center',
|
||||
},
|
||||
container: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '20vh',
|
||||
},
|
||||
video: {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
height: '33vh',
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
class Company extends React.Component {
|
||||
onVimeoError = (err) => {
|
||||
console.error(err);
|
||||
}
|
||||
render() {
|
||||
const { classes, company } = this.props;
|
||||
console.log(company);
|
||||
return (
|
||||
<LayoutDetail backLink="/Companies">
|
||||
<div className={classes.video}>
|
||||
<Plyr autoplay
|
||||
type="vimeo" // or "vimeo"
|
||||
videoId={Ar.last(_.split(company.presentation_video.cdn_url, '/'))}
|
||||
/>
|
||||
</div>
|
||||
<div className={classes.container}>
|
||||
<Typography gutterBottom variant="title" >
|
||||
{company.brand_name}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.container}>
|
||||
<Typography gutterBottom variant="subheading" >
|
||||
{company.description}
|
||||
</Typography>
|
||||
</div>
|
||||
{/* TODO:list of ads */}
|
||||
</LayoutDetail>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Company.getInitialProps = async function (context) {
|
||||
const { id } = context.query
|
||||
|
||||
return api.companies.fetchCompany(id);
|
||||
}
|
||||
|
||||
|
||||
Company.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default withStyles(styles)(Company);
|
||||
@@ -8,6 +8,8 @@ import LayoutGrid from "../components/templates/LayoutGrid";
|
||||
import Grid from "../components/templates/Grid"
|
||||
import OffreRow from "../components/organisms/OffreRow"
|
||||
|
||||
import api from '../data/api'
|
||||
|
||||
const styles = theme => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
@@ -26,21 +28,19 @@ const styles = theme => ({
|
||||
class Offres extends React.Component {
|
||||
|
||||
render() {
|
||||
const { classes, ads } = this.props;
|
||||
const { classes } = this.props;
|
||||
|
||||
return (
|
||||
<LayoutGrid backLink="/">
|
||||
<div className={classes.container}>
|
||||
<Grid CustomRowComponent={OffreRow} >
|
||||
<Grid CustomRowComponent={OffreRow} dataFetcher={api.ads.fetchAds}>
|
||||
</Grid>
|
||||
</div>
|
||||
</LayoutGrid>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Offres.propTypes = {
|
||||
classes: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
@@ -17,7 +17,7 @@ class MyDocument extends Document {
|
||||
return (
|
||||
<html lang="en" dir="ltr">
|
||||
<Head>
|
||||
<title>My page</title>
|
||||
|
||||
<meta charSet="utf-8" />
|
||||
{/* Use minimum-scale=1 to enable GPU rasterization */}
|
||||
<meta
|
||||
|
||||
@@ -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'
|
||||
import Loader from '../../components/templates/loader'
|
||||
|
||||
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}>
|
||||
<Loader fullscreen={true}/>
|
||||
</a>
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import Router from 'next/router'
|
||||
import Page from '../../components/templates/page'
|
||||
import Layout from '../../components/templates/layout'
|
||||
import { NextAuth } from 'next-auth/client'
|
||||
|
||||
export default class extends Page {
|
||||
|
||||
static async getInitialProps({req, res, query}) {
|
||||
let props = await super.getInitialProps({req})
|
||||
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 {...this.props} navmenu={false} signinBtn={false}>
|
||||
<div className="text-center pt-5 pb-5">
|
||||
<h1 className="display-4">Check your email</h1>
|
||||
<p className="lead">
|
||||
A sign in link has been sent to { (this.props.email) ? <span className="font-weight-bold">{this.props.email}</span> : <span>your inbox</span> }.
|
||||
</p>
|
||||
</div>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
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-4 mt-3 mb-3">NextAuth With Credentials</h1>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-sm-6 mr-auto ml-auto">
|
||||
<p>
|
||||
If you need password based sign in, two factor authentication
|
||||
or another sign in method, you can use a signin() function
|
||||
in <strong>next-auth.functions.js</strong>.
|
||||
</p>
|
||||
<p>
|
||||
You can pass in any properties you need – e.g. username and password,
|
||||
a PIN or 2FA Token – as properties of the object passed to
|
||||
NextAuth.signin() in the front end and they will be passed
|
||||
through to your signin() function.
|
||||
</p>
|
||||
<div className="card mt-3 mb-3">
|
||||
<h4 className="card-header">Sign In</h4>
|
||||
<div className="card-body pb-0">
|
||||
<p className="text-italic text-muted text-center small">
|
||||
<strong>Important!</strong> Enable the signin() function in <strong>next-auth.functions.js</strong> first.
|
||||
</p>
|
||||
<form id="signin" method="post" action="/auth/signin" onSubmit={this.handleSignInSubmit}>
|
||||
<input name="_csrf" type="hidden" value={this.state.session.csrfToken}/>
|
||||
<p>
|
||||
<label htmlFor="email">Email address</label><br/>
|
||||
<input name="email" type="text" placeholder="j.smith@example.com" id="email" className="form-control" value={this.state.email} onChange={this.handleEmailChange}/>
|
||||
</p>
|
||||
<p>
|
||||
<label htmlFor="password">Password</label><br/>
|
||||
<input name="password" type="password" placeholder="" id="password" className="form-control" value={this.state.password} onChange={this.handlePasswordChange}/>
|
||||
</p>
|
||||
<p className="text-right">
|
||||
<button id="submitButton" type="submit" className="btn btn-outline-primary">Sign in</button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-center">
|
||||
<Link href="/auth"><a>Back</a></Link>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import Page from '../../components/templates/page'
|
||||
import Layout from '../../components/templates/layoutAuth'
|
||||
|
||||
export default class extends Page {
|
||||
|
||||
static async getInitialProps({req, query}) {
|
||||
let props = await super.getInitialProps({req})
|
||||
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 {...this.props} navmenu={false}>
|
||||
<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 {...this.props} navmenu={false}>
|
||||
<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 {...this.props} navmenu={false}>
|
||||
<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>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,12 +95,12 @@ class Index extends React.Component {
|
||||
<Card className={classes.card} >
|
||||
<CardActionArea>
|
||||
<CardMedia
|
||||
onClick={() => Router.push('/Entreprises')}
|
||||
onClick={() => Router.push('/Companies')}
|
||||
component="img"
|
||||
className={classes.media}
|
||||
image={require('../images/667855903_640x360.jpg')}
|
||||
title="entreprises"
|
||||
/><Typography className={classes.title + ' ' + classes.white} variant="headline" >Entreprises</Typography>
|
||||
title="Companies"
|
||||
/><Typography className={classes.title + ' ' + classes.white} variant="headline" >Companies</Typography>
|
||||
</CardActionArea>
|
||||
</Card>
|
||||
</Grow>
|
||||
|
||||
+30
-15
@@ -1,5 +1,7 @@
|
||||
const express = require('express')
|
||||
const next = require('next')
|
||||
const nextAuth = require('next-auth')
|
||||
const nextAuthConfig = require('./next-auth.config')
|
||||
|
||||
const dev = process.env.NODE_ENV !== 'production'
|
||||
const app = next({ dev })
|
||||
@@ -7,26 +9,39 @@ const handle = app.getRequestHandler()
|
||||
|
||||
app.prepare()
|
||||
.then(() => {
|
||||
const server = express()
|
||||
|
||||
// Load configuration and return config object
|
||||
return nextAuthConfig()
|
||||
})
|
||||
.then(nextAuthOptions => {
|
||||
// Pass Next.js App instance and NextAuth options to NextAuth
|
||||
return nextAuth(app, nextAuthOptions)
|
||||
})
|
||||
.then(nextAuthOptions => {
|
||||
// Get Express and instance of Express from NextAuth
|
||||
const express = nextAuthOptions.express
|
||||
const expressApp = nextAuthOptions.expressApp
|
||||
|
||||
// server.get('/Offre?:id', (req, res) => {
|
||||
// const actualPage = '/Offre'
|
||||
// const queryParams = { id: req.params.id }
|
||||
// app.render(req, res, actualPage, queryParams)
|
||||
// })
|
||||
// server.get('/Offre/:id', (req, res) => {
|
||||
// const actualPage = '/Offre'
|
||||
// const queryParams = { id: req.params.id }
|
||||
// app.render(req, res, actualPage, queryParams)
|
||||
// })
|
||||
server.get('*', (req, res) => {
|
||||
expressApp.get('/Offre?:id', (req, res) => {
|
||||
const actualPage = '/Offre'
|
||||
const queryParams = { id: req.params.id }
|
||||
app.render(req, res, actualPage, queryParams)
|
||||
})
|
||||
|
||||
expressApp.get('/Offre/:id', (req, res) => {
|
||||
const actualPage = '/Offre'
|
||||
const queryParams = { id: req.params.id }
|
||||
app.render(req, res, actualPage, queryParams)
|
||||
})
|
||||
|
||||
expressApp.get('*', (req, res) => {
|
||||
return handle(req, res)
|
||||
})
|
||||
server.listen(3000, (err) => {
|
||||
|
||||
expressApp.listen(3000, (err) => {
|
||||
if (err) throw err
|
||||
console.log('> Ready on http://localhost:3000')
|
||||
console.log('> Ready on '+process.env.SERVER_URL)
|
||||
})
|
||||
|
||||
})
|
||||
.catch((ex) => {
|
||||
console.error(ex.stack)
|
||||
|
||||
+1347
-658
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user