This commit is contained in:
2019-05-27 14:36:05 +02:00
parent 96d599ba26
commit 23234a12c7
24 changed files with 2873 additions and 684 deletions
+72
View File
@@ -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);
+20
View File
@@ -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)
+26
View File
@@ -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)
+208
View File
@@ -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>
)
}
}
+101
View File
@@ -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>
)
}
}
}
+25
View File
@@ -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>
)
}
}
+103
View File
@@ -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>
)
}
}
+35
View File
@@ -49,5 +49,40 @@ export default {
return show.data; 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;
}
} }
} }
+81
View File
@@ -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
})
})
})
}
+273
View File
@@ -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
}
*/
})
})
}
+77
View File
@@ -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
}
+40
View File
@@ -1,6 +1,7 @@
// next.config.js // next.config.js
const withPlugins = require('next-compose-plugins'); const withPlugins = require('next-compose-plugins');
const optimizedImages = require('next-optimized-images'); const optimizedImages = require('next-optimized-images');
const webpack = require("webpack");
module.exports = withPlugins([ module.exports = withPlugins([
[optimizedImages, { [optimizedImages, {
@@ -8,5 +9,44 @@ module.exports = withPlugins([
}], }],
// your other plugins here // 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
View File
@@ -2,29 +2,47 @@
"name": "nextjs", "name": "nextjs",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"resolutions": {
"terser": "3.14.1"
},
"dependencies": { "dependencies": {
"@material-ui/core": "latest", "@material-ui/core": "latest",
"@material-ui/icons": "^3.0.1", "@material-ui/icons": "^3.0.1",
"connect-mongo": "^2.0.1",
"cross-env": "^5.2.0",
"dotenv": "^6.0.0",
"eslint": "^5.6.0", "eslint": "^5.6.0",
"express": "^4.16.3", "express": "^4.16.3",
"griddle-react": "^1.13.1", "griddle-react": "^1.13.1",
"isomorphic-unfetch": "^3.0.0", "isomorphic-unfetch": "^3.0.0",
"jss": "latest", "jss": "latest",
"lodash": "^4.17.11", "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-compose-plugins": "^2.1.1",
"next-optimized-images": "^1.4.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", "prop-types": "latest",
"react": "latest", "react": "latest",
"react-dom": "latest", "react-dom": "latest",
"react-jss": "latest", "react-jss": "latest",
"react-plyr": "^2.1.1", "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": { "scripts": {
"dev": "node server.js", "dev": "node server.js",
"build": "next build", "build": "next build",
"start": "NODE_ENV=production node server.js" "start": "cross-env NODE_ENV=production node server.js"
}, },
"eslint.packageManager": "yarn" "eslint.packageManager": "yarn"
} }
+49
View File
@@ -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);
+77
View File
@@ -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);
+4 -4
View File
@@ -8,6 +8,8 @@ import LayoutGrid from "../components/templates/LayoutGrid";
import Grid from "../components/templates/Grid" import Grid from "../components/templates/Grid"
import OffreRow from "../components/organisms/OffreRow" import OffreRow from "../components/organisms/OffreRow"
import api from '../data/api'
const styles = theme => ({ const styles = theme => ({
root: { root: {
position: 'relative', position: 'relative',
@@ -26,21 +28,19 @@ const styles = theme => ({
class Offres extends React.Component { class Offres extends React.Component {
render() { render() {
const { classes, ads } = this.props; const { classes } = this.props;
return ( return (
<LayoutGrid backLink="/"> <LayoutGrid backLink="/">
<div className={classes.container}> <div className={classes.container}>
<Grid CustomRowComponent={OffreRow} > <Grid CustomRowComponent={OffreRow} dataFetcher={api.ads.fetchAds}>
</Grid> </Grid>
</div> </div>
</LayoutGrid> </LayoutGrid>
); );
} }
} }
Offres.propTypes = { Offres.propTypes = {
classes: PropTypes.object.isRequired, classes: PropTypes.object.isRequired,
}; };
+1 -1
View File
@@ -17,7 +17,7 @@ class MyDocument extends Document {
return ( return (
<html lang="en" dir="ltr"> <html lang="en" dir="ltr">
<Head> <Head>
<title>My page</title>
<meta charSet="utf-8" /> <meta charSet="utf-8" />
{/* Use minimum-scale=1 to enable GPU rasterization */} {/* Use minimum-scale=1 to enable GPU rasterization */}
<meta <meta
+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'
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>
)
}
}
+40
View File
@@ -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>
)
}
}
+115
View File
@@ -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>
)
}
}
}
+68
View File
@@ -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>
)
}
}
}
+3 -3
View File
@@ -95,12 +95,12 @@ class Index extends React.Component {
<Card className={classes.card} > <Card className={classes.card} >
<CardActionArea> <CardActionArea>
<CardMedia <CardMedia
onClick={() => Router.push('/Entreprises')} onClick={() => Router.push('/Companies')}
component="img" component="img"
className={classes.media} className={classes.media}
image={require('../images/667855903_640x360.jpg')} image={require('../images/667855903_640x360.jpg')}
title="entreprises" title="Companies"
/><Typography className={classes.title + ' ' + classes.white} variant="headline" >Entreprises</Typography> /><Typography className={classes.title + ' ' + classes.white} variant="headline" >Companies</Typography>
</CardActionArea> </CardActionArea>
</Card> </Card>
</Grow> </Grow>
+30 -15
View File
@@ -1,5 +1,7 @@
const express = require('express') const express = require('express')
const next = require('next') const next = require('next')
const nextAuth = require('next-auth')
const nextAuthConfig = require('./next-auth.config')
const dev = process.env.NODE_ENV !== 'production' const dev = process.env.NODE_ENV !== 'production'
const app = next({ dev }) const app = next({ dev })
@@ -7,26 +9,39 @@ const handle = app.getRequestHandler()
app.prepare() app.prepare()
.then(() => { .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) => { expressApp.get('/Offre?:id', (req, res) => {
// const actualPage = '/Offre' const actualPage = '/Offre'
// const queryParams = { id: req.params.id } const queryParams = { id: req.params.id }
// app.render(req, res, actualPage, queryParams) app.render(req, res, actualPage, queryParams)
// }) })
// server.get('/Offre/:id', (req, res) => {
// const actualPage = '/Offre' expressApp.get('/Offre/:id', (req, res) => {
// const queryParams = { id: req.params.id } const actualPage = '/Offre'
// app.render(req, res, actualPage, queryParams) const queryParams = { id: req.params.id }
// }) app.render(req, res, actualPage, queryParams)
server.get('*', (req, res) => { })
expressApp.get('*', (req, res) => {
return handle(req, res) return handle(req, res)
}) })
server.listen(3000, (err) => {
expressApp.listen(3000, (err) => {
if (err) throw err if (err) throw err
console.log('> Ready on http://localhost:3000') console.log('> Ready on '+process.env.SERVER_URL)
}) })
}) })
.catch((ex) => { .catch((ex) => {
console.error(ex.stack) console.error(ex.stack)
+1347 -658
View File
File diff suppressed because it is too large Load Diff