feat: add docker support and update deps

This commit is contained in:
divlo 2020-10-30 16:58:27 +01:00
parent f5a8be5000
commit ffec0058e5
35 changed files with 5006 additions and 4502 deletions

2
api/.dockerignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
build

View File

@ -1,12 +1,15 @@
HOST = "http://localhost:8080" HOST="http://localhost:8080"
FRONT_END_HOST = "http://localhost:3000" FRONT_END_HOST="http://localhost:3000"
OpenWeatherMap_API_KEY = "" OpenWeatherMap_API_KEY=""
Scraper_API_KEY = "" Scraper_API_KEY=""
DB_HOST = "" DATABASE_HOST="functionproject-database"
DB_NAME = "" DATABASE_NAME="functionproject"
DB_USER = "" DATABASE_USER="root"
DB_PASS = "" DATABASE_PASSWORD="password"
JWT_SECRET = "" DATABASE_PORT=3306
EMAIL_HOST = "" JWT_SECRET=""
EMAIL_USER = "" EMAIL_HOST="functionproject-maildev"
EMAIL_PASSWORD = "" EMAIL_USER="no-reply@functionproject.fr"
EMAIL_PASSWORD="password"
EMAIL_PORT=25
COMPOSE_PROJECT_NAME="function.divlo.fr-api"

6
api/.gitignore vendored
View File

@ -11,14 +11,16 @@
# production # production
/build /build
# misc # envs
.DS_Store
.env .env
.env.local .env.local
.env.development.local .env.development.local
.env.test.local .env.test.local
.env.production.local .env.production.local
.env.production .env.production
# misc
.DS_Store
/temp /temp
/assets/images/ /assets/images/

13
api/Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM node:14.15.0-alpine3.12
WORKDIR /app
COPY ./package*.json ./
RUN npm install
COPY ./ ./
# docker-compose-wait
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
RUN chmod +x /wait
CMD /wait && npm run dev

View File

@ -16,11 +16,14 @@ const isAdmin = require('./middlewares/isAdmin')
const app = express() const app = express()
/* Middlewares */ /* Middlewares */
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'))
} else if (process.env.NODE_ENV === 'production') {
app.use(redirectToHTTPS())
}
app.use(helmet()) app.use(helmet())
app.use(cors()) app.use(cors())
app.use(morgan('dev'))
app.use(express.json()) app.use(express.json())
app.use(redirectToHTTPS([/localhost:(\d{4})/]))
/* Routes */ /* Routes */
app.use('/images', express.static(path.join(__dirname, 'assets', 'images'))) app.use('/images', express.static(path.join(__dirname, 'assets', 'images')))
@ -37,7 +40,7 @@ app.use('/links', require('./routes/links_shortener'))
/* Errors Handling */ /* Errors Handling */
app.use((_req, _res, next) => app.use((_req, _res, next) =>
errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" }) errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })
) // 404 )
app.use((error, _req, res, _next) => { app.use((error, _req, res, _next) => {
console.log(error) console.log(error)
const { statusCode, message } = error const { statusCode, message } = error
@ -83,7 +86,6 @@ Users.hasMany(ShortLinks)
ShortLinks.belongsTo(Users, { constraints: false }) ShortLinks.belongsTo(Users, { constraints: false })
/* Server */ /* Server */
// sequelize.sync({ force: true })
sequelize sequelize
.sync() .sync()
.then(() => { .then(() => {

View File

@ -1,3 +1,8 @@
const dotenv = require('dotenv')
dotenv.config()
const EMAIL_PORT = parseInt(process.env.EMAIL_PORT ?? '465', 10)
const config = { const config = {
PORT: process.env.PORT || 8080, PORT: process.env.PORT || 8080,
HOST: process.env.HOST, HOST: process.env.HOST,
@ -5,20 +10,22 @@ const config = {
WEATHER_API_KEY: process.env.OpenWeatherMap_API_KEY, WEATHER_API_KEY: process.env.OpenWeatherMap_API_KEY,
SCRAPER_API_KEY: process.env.Scraper_API_KEY, SCRAPER_API_KEY: process.env.Scraper_API_KEY,
DATABASE: { DATABASE: {
host: process.env.DB_HOST, host: process.env.DATABASE_HOST,
name: process.env.DB_NAME, name: process.env.DATABASE_NAME,
user: process.env.DB_USER, user: process.env.DATABASE_USER,
password: process.env.DB_PASS password: process.env.DATABASE_PASSWORD,
port: parseInt(process.env.DATABASE_PORT ?? '3306', 10)
}, },
JWT_SECRET: process.env.JWT_SECRET, JWT_SECRET: process.env.JWT_SECRET,
EMAIL_INFO: { EMAIL_INFO: {
host: process.env.EMAIL_HOST, host: process.env.EMAIL_HOST,
port: 465, port: EMAIL_PORT,
secure: true, // true for 465, false for other ports secure: EMAIL_PORT === 465,
auth: { auth: {
user: process.env.EMAIL_USER, user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD pass: process.env.EMAIL_PASSWORD
} },
ignoreTLS: process.env.NODE_ENV !== 'production'
}, },
TOKEN_LIFE: '1 week' TOKEN_LIFE: '1 week'
} }

View File

@ -7,7 +7,8 @@ const sequelize = new Sequelize(
DATABASE.password, DATABASE.password,
{ {
dialect: 'mysql', dialect: 'mysql',
host: DATABASE.host host: DATABASE.host,
port: DATABASE.port
} }
) )

1649
api/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,33 +5,33 @@
"scripts": { "scripts": {
"start": "node app.js", "start": "node app.js",
"dev": "nodemon app.js", "dev": "nodemon app.js",
"format": "standard \"./**/*.js\" --fix | snazzy || exit 0" "format": "standard \"./**/*.js\" --fix | snazzy"
}, },
"dependencies": { "dependencies": {
"axios": "^0.19.2", "axios": "^0.21.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"cors": "^2.8.5", "cors": "^2.8.5",
"express": "^4.17.1", "express": "^4.17.1",
"express-fileupload": "^1.1.6", "express-fileupload": "^1.2.0",
"express-http-to-https": "^1.1.4", "express-http-to-https": "^1.1.4",
"express-validator": "^6.4.0", "express-validator": "^6.6.1",
"helmet": "^3.21.3", "helmet": "^4.1.1",
"jsdom": "^16.2.2", "jsdom": "^16.4.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"moment": "^2.24.0", "moment": "^2.29.1",
"ms": "^2.1.2", "ms": "^2.1.2",
"mysql2": "^2.1.0", "mysql2": "^2.2.5",
"nodemailer": "^6.4.6", "nodemailer": "^6.4.14",
"sequelize": "^5.21.5", "sequelize": "^6.3.5",
"smart-request-balancer": "^2.1.1", "smart-request-balancer": "^2.1.1",
"uuid": "^7.0.2", "uuid": "^8.3.1",
"validator": "^13.0.0", "validator": "^13.1.17",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"morgan": "^1.9.1" "morgan": "^1.10.0"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.4", "nodemon": "^2.0.6",
"snazzy": "^8.0.0", "snazzy": "^9.0.0",
"standard": "^14.3.4" "standard": "^16.0.0"
} }
} }

73
docker-compose.yml Normal file
View File

@ -0,0 +1,73 @@
version: '3.0'
services:
functionproject-api:
build:
context: './api'
ports:
- '8080:8080'
depends_on:
- 'functionproject-database'
- 'functionproject-maildev'
volumes:
- './api:/app'
- '/app/node_modules'
environment:
WAIT_HOSTS: 'functionproject-database:3306'
container_name: 'functionproject-api'
s.divlo.fr-website:
build:
context: './s.divlo.fr'
ports:
- '7000:7000'
depends_on:
- 'functionproject-database'
volumes:
- './s.divlo.fr:/app'
- '/app/node_modules'
environment:
WAIT_HOSTS: 'functionproject-database:3306'
container_name: 's.divlo.fr-website'
functionproject-website:
build:
context: './website'
ports:
- '3000:3000'
volumes:
- './website:/app'
- '/app/node_modules'
container_name: 'functionproject-website'
functionproject-phpmyadmin:
image: 'phpmyadmin/phpmyadmin:5.0.2'
environment:
PMA_HOST: 'functionproject-database'
PMA_USER: 'root'
PMA_PASSWORD: 'password'
ports:
- '8000:80'
depends_on:
- 'functionproject-database'
container_name: 'functionproject-phpmyadmin'
functionproject-database:
image: 'mysql:5.7'
command: '--default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci'
environment:
MYSQL_ROOT_PASSWORD: 'password'
MYSQL_DATABASE: 'functionproject'
ports:
- '3306:3306'
volumes:
- 'database-volume:/var/lib/mysql'
container_name: 'functionproject-database'
functionproject-maildev:
image: 'maildev/maildev:1.1.0'
ports:
- '1080:80'
container_name: 'functionproject-maildev'
volumes:
database-volume:

2
s.divlo.fr/.dockerignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
build

View File

@ -1,4 +1,6 @@
DB_HOST = "" DATABASE_HOST="functionproject-database"
DB_NAME = "" DATABASE_NAME="functionproject"
DB_USER = "" DATABASE_USER="root"
DB_PASS = "" DATABASE_PASSWORD="password"
DATABASE_PORT=3306
COMPOSE_PROJECT_NAME="s.divlo.fr-website"

13
s.divlo.fr/Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM node:14.15.0-alpine3.12
WORKDIR /app
COPY ./package*.json ./
RUN npm install
COPY ./ ./
# docker-compose-wait
ADD https://github.com/ufoscout/docker-compose-wait/releases/download/2.7.3/wait /wait
RUN chmod +x /wait
CMD /wait && npm run dev

View File

@ -10,18 +10,21 @@ const mysql = require('mysql')
/* Files Imports & Variables */ /* Files Imports & Variables */
const app = express() const app = express()
const database = mysql.createPool({ const database = mysql.createPool({
host: process.env.DB_HOST, host: process.env.DATABASE_HOST,
user: process.env.DB_USER, user: process.env.DATABASE_USER,
password: process.env.DB_PASS, password: process.env.DATABASE_PASS,
database: process.env.DB_NAME, database: process.env.DATABASE_NAME,
port: process.env.DB_PORT port: process.env.DATABASE_PORT
}) })
/* Middlewares */ /* Middlewares */
if (process.env.NODE_ENV === 'development') {
app.use(morgan('dev'))
} else if (process.env.NODE_ENV === 'production') {
app.use(redirectToHTTPS())
}
app.use(helmet()) app.use(helmet())
app.use(morgan('dev'))
app.use(express.json()) app.use(express.json())
app.use(redirectToHTTPS([/localhost:(\d{4})/]))
/* EJS Template Engines */ /* EJS Template Engines */
app.set('view engine', 'ejs') app.set('view engine', 'ejs')
@ -77,7 +80,7 @@ app.use((error, _req, res) => {
}) })
/* Server */ /* Server */
const PORT = process.env.PORT || 8000 const PORT = process.env.PORT || 7000
app.listen(PORT, () => { app.listen(PORT, () => {
console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`) console.log('\x1b[36m%s\x1b[0m', `Started on port ${PORT}.`)
}) })

File diff suppressed because it is too large Load Diff

View File

@ -9,16 +9,16 @@
}, },
"dependencies": { "dependencies": {
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"ejs": "^3.1.3", "ejs": "^3.1.5",
"express": "^4.17.1", "express": "^4.17.1",
"express-http-to-https": "^1.1.4", "express-http-to-https": "^1.1.4",
"helmet": "^4.0.0", "helmet": "^4.1.1",
"morgan": "^1.10.0", "morgan": "^1.10.0",
"mysql": "^2.18.1" "mysql": "^2.18.1"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^2.0.4", "nodemon": "^2.0.6",
"snazzy": "^8.0.0", "snazzy": "^9.0.0",
"standard": "^14.3.4" "standard": "^16.0.0"
} }
} }

2
website/.dockerignore Normal file
View File

@ -0,0 +1,2 @@
node_modules
build

View File

@ -1 +1,3 @@
NEXT_PUBLIC_API_URL = "http://localhost:8080" NEXT_PUBLIC_API_URL = "http://localhost:8080"
CONTAINER_API_URL="http://functionproject-api:8080"
COMPOSE_PROJECT_NAME="function.divlo.fr-website"

9
website/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM node:14.15.0-alpine3.12
WORKDIR /app
COPY ./package*.json ./
RUN npm install
COPY ./ ./
CMD ["npm", "run", "dev"]

View File

@ -23,15 +23,15 @@ const FunctionCard = memo(
<Link <Link
{...(props.isAdmin {...(props.isAdmin
? { ? {
href: '/admin/[slug]', href: '/admin/[slug]',
as: `/admin/${props.slug}` as: `/admin/${props.slug}`
} }
: { : {
href: isFormOrArticle href: isFormOrArticle
? '/functions/[slug]' ? '/functions/[slug]'
: `/functions/${props.slug}`, : `/functions/${props.slug}`,
as: `/functions/${props.slug}` as: `/functions/${props.slug}`
})} })}
> >
{/* FunctionCard a une hauteur pendant chargement */} {/* FunctionCard a une hauteur pendant chargement */}
<a <a

View File

@ -82,62 +82,68 @@ const CommentCard = forwardRef((props, ref) => {
<a>{props.user.name}</a> <a>{props.user.name}</a>
</Link> </Link>
&nbsp;-{' '} &nbsp;-{' '}
{date.format(new Date(props.createdAt), 'DD/MM/YYYY à HH:mm', false)} {date.format(
new Date(props.createdAt),
'DD/MM/YYYY à HH:mm',
false
)}
</span> </span>
</div> </div>
<div className='row'> <div className='row'>
<div className='col-24'> <div className='col-24'>
{!isEditing ? ( {!isEditing
<> ? (
<div className='CommentCard__message'> <>
<ReactMarkdown <div className='CommentCard__message'>
source={editInput} <ReactMarkdown
renderers={{ code: CodeBlock }} source={editInput}
/> renderers={{ code: CodeBlock }}
</div> />
{isAuth && user.name === props.user.name && ( </div>
<p {isAuth && user.name === props.user.name && (
style={{ <p
fontSize: '15px', style={{
margin: '15px 0 0 0', fontSize: '15px',
fontStyle: 'italic' margin: '15px 0 0 0',
}} fontStyle: 'italic'
> }}
<a onClick={deleteCommentById} href='#'> >
supprimer <a onClick={deleteCommentById} href='#'>
</a> supprimer
</a>
&nbsp;-&nbsp; &nbsp;-&nbsp;
<a style={{ cursor: 'pointer' }} onClick={editComment}> <a style={{ cursor: 'pointer' }} onClick={editComment}>
modifier modifier
</a> </a>
</p> </p>
)}
</>
)
: (
<form onSubmit={handleSubmit}>
<div className='form-group FunctionComments__post-group'>
<label className='form-label' htmlFor='commentEdit'>
Modifier le commentaire :
</label>
<textarea
style={{ height: 'auto' }}
value={editInput}
onChange={handleChange}
name='commentEdit'
id='commentEdit'
className='form-control'
rows='5'
placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)"
/>
</div>
<div className='form-group' style={{ marginTop: '0.7em' }}>
<button type='submit' className='btn btn-dark'>
Envoyer
</button>
</div>
<div className='text-center'>{htmlParser(message)}</div>
</form>
)} )}
</>
) : (
<form onSubmit={handleSubmit}>
<div className='form-group FunctionComments__post-group'>
<label className='form-label' htmlFor='commentEdit'>
Modifier le commentaire :
</label>
<textarea
style={{ height: 'auto' }}
value={editInput}
onChange={handleChange}
name='commentEdit'
id='commentEdit'
className='form-control'
rows='5'
placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)"
/>
</div>
<div className='form-group' style={{ marginTop: '0.7em' }}>
<button type='submit' className='btn btn-dark'>
Envoyer
</button>
</div>
<div className='text-center'>{htmlParser(message)}</div>
</form>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,11 +3,13 @@ import htmlParser from 'html-react-parser'
const FunctionArticle = ({ article }) => { const FunctionArticle = ({ article }) => {
return ( return (
<div style={{ marginBottom: '50px' }} className='container-fluid'> <div style={{ marginBottom: '50px' }} className='container-fluid'>
{article != null ? ( {article != null
htmlParser(article) ? (
) : ( htmlParser(article)
<p className='text-center'>L'article n'est pas encore disponible.</p> )
)} : (
<p className='text-center'>L'article n'est pas encore disponible.</p>
)}
</div> </div>
) )
} }

View File

@ -90,36 +90,38 @@ const FunctionComments = ({ functionId }) => {
<div className='FunctionComments__post container-fluid'> <div className='FunctionComments__post container-fluid'>
<div className='row FunctionComments__row'> <div className='row FunctionComments__row'>
<div className='col-24'> <div className='col-24'>
{isAuth ? ( {isAuth
<form onSubmit={handleSubmit}> ? (
<div className='form-group FunctionComments__post-group'> <form onSubmit={handleSubmit}>
<label className='form-label' htmlFor='commentPost'> <div className='form-group FunctionComments__post-group'>
Ajouter un commentaire : <label className='form-label' htmlFor='commentPost'>
</label> Ajouter un commentaire :
<textarea </label>
className='FunctionComments__textarea form-control' <textarea
value={inputState.commentPost} className='FunctionComments__textarea form-control'
onChange={handleChange} value={inputState.commentPost}
name='commentPost' onChange={handleChange}
id='commentPost' name='commentPost'
placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)" id='commentPost'
/> placeholder="Idée d'amélioration, avis, remarque, partage d'expérience personnel, ... (Markdown autorisé)"
</div> />
<div className='form-group' style={{ marginTop: '0.7em' }}> </div>
<button type='submit' className='btn btn-dark'> <div className='form-group' style={{ marginTop: '0.7em' }}>
Envoyer <button type='submit' className='btn btn-dark'>
</button> Envoyer
</div> </button>
</form> </div>
) : ( </form>
<p className='text-center'> )
Vous devez être{' '} : (
<Link href='/users/login'> <p className='text-center'>
<a>connecté</a> Vous devez être{' '}
</Link>{' '} <Link href='/users/login'>
pour poster un commentaire. <a>connecté</a>
</p> </Link>{' '}
)} pour poster un commentaire.
</p>
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -51,35 +51,37 @@ export default function Header () {
<NavigationLink name='Accueil' path='/' /> <NavigationLink name='Accueil' path='/' />
<NavigationLink name='Fonctions' path='/functions' /> <NavigationLink name='Fonctions' path='/functions' />
<NavigationLink name='Utilisateurs' path='/users' /> <NavigationLink name='Utilisateurs' path='/users' />
{!isAuth ? ( {!isAuth
<> ? (
<NavigationLink name="S'inscrire" path='/users/register' /> <>
<NavigationLink name='Connexion' path='/users/login' /> <NavigationLink name="S'inscrire" path='/users/register' />
</> <NavigationLink name='Connexion' path='/users/login' />
) : ( </>
<> )
<li className='navbar-item'> : (
<Link href='/users/[name]' as={`/users/${user.name}`}> <>
<a <li className='navbar-item'>
className={`navbar-link ${ <Link href='/users/[name]' as={`/users/${user.name}`}>
<a
className={`navbar-link ${
pathname === '/users/[name]' pathname === '/users/[name]'
? 'navbar-link-active' ? 'navbar-link-active'
: null : null
}`} }`}
> >
Mon Profil Mon Profil
</a> </a>
</Link> </Link>
</li> </li>
<li className='navbar-item'> <li className='navbar-item'>
<Link href='/'> <Link href='/'>
<a onClick={logoutUser} className='navbar-link'> <a onClick={logoutUser} className='navbar-link'>
Se déconnecter Se déconnecter
</a> </a>
</Link> </Link>
</li> </li>
</> </>
)} )}
{isAuth && user.isAdmin && ( {isAuth && user.isAdmin && (
<NavigationLink name='Admin' path='/admin' /> <NavigationLink name='Admin' path='/admin' />
)} )}

4268
website/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,44 +4,46 @@
"description": "Website frontend for FunctionProject", "description": "Website frontend for FunctionProject",
"main": "server.js", "main": "server.js",
"scripts": { "scripts": {
"dev": "cross-env NODE_ENV=development node server", "dev:custom": "cross-env NODE_ENV=development node server",
"start:custom": "cross-env NODE_ENV=production node server",
"dev": "next",
"start": "next start",
"build": "next build", "build": "next build",
"export": "next export", "export": "next export",
"start": "cross-env NODE_ENV=production node server", "format": "standard \"./**/*.{js,jsx}\" --fix | snazzy"
"format": "standard \"./**/*.{js,jsx}\" --fix | snazzy || exit 0"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.28", "@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-brands-svg-icons": "^5.13.0", "@fortawesome/free-brands-svg-icons": "^5.15.1",
"@fortawesome/free-regular-svg-icons": "^5.13.0", "@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.13.0", "@fortawesome/free-solid-svg-icons": "^5.15.1",
"@fortawesome/react-fontawesome": "^0.1.9", "@fortawesome/react-fontawesome": "^0.1.12",
"@zeit/next-css": "^1.0.1", "@zeit/next-css": "^1.0.1",
"axios": "^0.19.2", "axios": "^0.21.0",
"date-and-time": "^0.13.1", "date-and-time": "^0.14.1",
"date-fns": "^2.12.0", "date-fns": "^2.16.1",
"express": "^4.17.1", "express": "^4.17.1",
"express-http-to-https": "^1.1.4", "express-http-to-https": "^1.1.4",
"html-react-parser": "^0.10.2", "html-react-parser": "^0.14.0",
"next": "^9.5.4", "next": "^10.0.0",
"next-fonts": "^1.0.3", "next-fonts": "^1.4.0",
"notyf": "^3.6.0", "notyf": "^3.9.0",
"nprogress": "^0.2.0", "nprogress": "^0.2.0",
"react": "16.13.0", "react": "17.0.1",
"react-codepen-embed": "^1.0.1", "react-codepen-embed": "^1.0.1",
"react-color": "^2.18.0", "react-color": "^2.19.3",
"react-datepicker": "^2.14.1", "react-datepicker": "^3.3.0",
"react-dom": "16.13.0", "react-dom": "17.0.1",
"react-markdown": "^4.3.1", "react-markdown": "^5.0.2",
"react-swipeable-views": "^0.13.9", "react-swipeable-views": "^0.13.9",
"react-swipeable-views-utils": "^0.13.9", "react-swipeable-views-utils": "^0.13.9",
"react-syntax-highlighter": "^12.2.1", "react-syntax-highlighter": "^15.3.0",
"suneditor-react": "^2.8.0", "suneditor-react": "^2.8.0",
"universal-cookie": "^4.0.3" "universal-cookie": "^4.0.4"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"snazzy": "^8.0.0", "snazzy": "^9.0.0",
"standard": "^14.3.4" "standard": "^16.0.0"
} }
} }

View File

@ -23,64 +23,66 @@ const Admin = props => {
/> />
{/* Création d'une fonction */} {/* Création d'une fonction */}
{isOpen ? ( {isOpen
<Modal toggleModal={toggleModal}> ? (
<div className='Admin__Modal__container container-fluid'> <Modal toggleModal={toggleModal}>
<div className='Admin__Modal__row row'> <div className='Admin__Modal__container container-fluid'>
<div className='col-24'> <div className='Admin__Modal__row row'>
<div className='Admin__Modal-top-container row'> <div className='col-24'>
<div className='col-24'> <div className='Admin__Modal-top-container row'>
<span <div className='col-24'>
onClick={toggleModal} <span
style={{ onClick={toggleModal}
cursor: 'pointer', style={{
position: 'absolute', cursor: 'pointer',
left: 0 position: 'absolute',
}} left: 0
> }}
<FontAwesomeIcon >
icon={faTimes} <FontAwesomeIcon
style={{ width: '1.5rem', color: 'red' }} icon={faTimes}
/> style={{ width: '1.5rem', color: 'red' }}
</span> />
<h2 className='text-center'>Crée une nouvelle fonction</h2> </span>
<h2 className='text-center'>Crée une nouvelle fonction</h2>
</div>
</div> </div>
</div> </div>
</div>
<div className='col-24'> <div className='col-24'>
<AddEditFunction <AddEditFunction
defaultInputState={{ type: 'form' }} defaultInputState={{ type: 'form' }}
{...props} {...props}
/> />
</div>
</div> </div>
</div> </div>
</div> </Modal>
</Modal> )
) : ( : (
<FunctionsList isAdmin token={props.user.token}> <FunctionsList isAdmin token={props.user.token}>
<div className='col-24'> <div className='col-24'>
<h1 className='Functions__title'>Administration</h1> <h1 className='Functions__title'>Administration</h1>
<button <button
onClick={toggleModal} onClick={toggleModal}
style={{ margin: '0 0 40px 0' }} style={{ margin: '0 0 40px 0' }}
className='btn btn-dark' className='btn btn-dark'
> >
Crée une nouvelle fonction Crée une nouvelle fonction
</button>
<Link href='/admin/manageCategories'>
<button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>
Gérer les catégories
</button> </button>
</Link> <Link href='/admin/manageCategories'>
<Link href='/admin/manageQuotes'> <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>
<button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'> Gérer les catégories
Gérer les citations </button>
</button> </Link>
</Link> <Link href='/admin/manageQuotes'>
</div> <button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>
</FunctionsList> Gérer les citations
)} </button>
</Link>
</div>
</FunctionsList>
)}
</> </>
) )
} }

View File

@ -159,119 +159,121 @@ const manageCategories = props => {
description="Page d'administration de FunctionProject. Gérer les catégories." description="Page d'administration de FunctionProject. Gérer les catégories."
/> />
{isOpen ? ( {isOpen
<Modal> ? (
<AddEditCategory <Modal>
handleToggleModal={toggleModal} <AddEditCategory
defaultInputState={defaultInputState} handleToggleModal={toggleModal}
{...props} defaultInputState={defaultInputState}
isEditing={isEditing} {...props}
/> isEditing={isEditing}
</Modal> />
) : ( </Modal>
<div className='container-fluid text-center'> )
<div className='row justify-content-center'> : (
<div className='col-24'> <div className='container-fluid text-center'>
<h1>Gérer les catégories</h1> <div className='row justify-content-center'>
<button <div className='col-24'>
onClick={() => { <h1>Gérer les catégories</h1>
setDefaultInputState(defaultCategoryState) <button
toggleModal() onClick={() => {
setIsEditing(false) setDefaultInputState(defaultCategoryState)
}} toggleModal()
style={{ margin: '0 0 40px 0' }} setIsEditing(false)
className='btn btn-dark' }}
> style={{ margin: '0 0 40px 0' }}
Ajouter une catégorie className='btn btn-dark'
</button> >
Ajouter une catégorie
</button>
</div>
</div> </div>
</div> <div className='row justify-content-center'>
<div className='row justify-content-center'> <div className='container-fluid'>
<div className='container-fluid'> <div className='col-24 table-column'>
<div className='col-24 table-column'> <table className='table'>
<table className='table'> <thead>
<thead> <tr>
<tr> <th className='table-row' scope='col'>
<th className='table-row' scope='col'> id
id </th>
</th> <th className='table-row' scope='col'>
<th className='table-row' scope='col'> name
name </th>
</th> <th className='table-row' scope='col'>
<th className='table-row' scope='col'> color
color </th>
</th> <th className='table-row' scope='col'>
<th className='table-row' scope='col'> createdAt
createdAt </th>
</th> <th className='table-row' scope='col'>
<th className='table-row' scope='col'> updatedAt
updatedAt </th>
</th> <th className='table-row' scope='col'>
<th className='table-row' scope='col'> Modifier
Modifier </th>
</th> <th className='table-row' scope='col'>
<th className='table-row' scope='col'> Supprimer
Supprimer </th>
</th> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> {categories.map(category => {
{categories.map(category => { return (
return ( <tr
<tr key={category.id}
key={category.id} style={{ backgroundColor: category.color }}
style={{ backgroundColor: category.color }}
>
<td className='table-row'>{category.id}</td>
<td className='table-row'>{category.name}</td>
<td className='table-row'>{category.color}</td>
<td className='table-row'>
{date.format(
new Date(category.createdAt),
'DD/MM/YYYY à HH:mm',
false
)}
</td>
<td className='table-row'>
{date.format(
new Date(category.updatedAt),
'DD/MM/YYYY à HH:mm',
false
)}
</td>
<td
style={{ cursor: 'pointer' }}
onClick={() =>
handleEditCategory({
name: category.name,
color: category.color,
id: category.id
})}
> >
<FontAwesomeIcon <td className='table-row'>{category.id}</td>
icon={faPen} <td className='table-row'>{category.name}</td>
style={{ width: '1.5rem' }} <td className='table-row'>{category.color}</td>
/> <td className='table-row'>
</td> {date.format(
<td new Date(category.createdAt),
style={{ cursor: 'pointer' }} 'DD/MM/YYYY à HH:mm',
onClick={() => handleRemoveCategory(category.id)} false
> )}
<FontAwesomeIcon </td>
icon={faTrash} <td className='table-row'>
style={{ width: '1.5rem' }} {date.format(
/> new Date(category.updatedAt),
</td> 'DD/MM/YYYY à HH:mm',
</tr> false
) )}
})} </td>
</tbody> <td
</table> style={{ cursor: 'pointer' }}
onClick={() =>
handleEditCategory({
name: category.name,
color: category.color,
id: category.id
})}
>
<FontAwesomeIcon
icon={faPen}
style={{ width: '1.5rem' }}
/>
</td>
<td
style={{ cursor: 'pointer' }}
onClick={() => handleRemoveCategory(category.id)}
>
<FontAwesomeIcon
icon={faTrash}
style={{ width: '1.5rem' }}
/>
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> )}
)}
</> </>
) )
} }

View File

@ -266,144 +266,146 @@ const LinksList = ({
</div> </div>
<div className='row justify-content-center'> <div className='row justify-content-center'>
<div className='container-fluid'> <div className='container-fluid'>
{!isEditing ? ( {!isEditing
<div className='col-24 table-column'> ? (
<table className='table' style={{ marginBottom: '40px' }}> <div className='col-24 table-column'>
<thead> <table className='table' style={{ marginBottom: '40px' }}>
<tr> <thead>
<th className='table-row' scope='col'> <tr>
Liens <th className='table-row' scope='col'>
</th> Liens
<th className='table-row' scope='col'> </th>
Nom <th className='table-row' scope='col'>
</th> Nom
<th className='table-row' scope='col'> </th>
Compteur de clics <th className='table-row' scope='col'>
</th> Compteur de clics
<th className='table-row' scope='col'> </th>
Modifier <th className='table-row' scope='col'>
</th> Modifier
<th className='table-row' scope='col'> </th>
Supprimer <th className='table-row' scope='col'>
</th> Supprimer
</tr> </th>
</thead> </tr>
<tbody> </thead>
{linksData.rows.map((link, index) => { <tbody>
const linkJSX = ( {linksData.rows.map((link, index) => {
<> const linkJSX = (
<td className='table-row'> <>
<a href={link.url}>{link.url}</a> <td className='table-row'>
</td> <a href={link.url}>{link.url}</a>
<td className='table-row'> </td>
<a href={`https://s.divlo.fr/${link.shortcut}`}> <td className='table-row'>
{link.shortcut} <a href={`https://s.divlo.fr/${link.shortcut}`}>
</a> {link.shortcut}
</td> </a>
<td className='table-row'>{link.count}</td> </td>
<td <td className='table-row'>{link.count}</td>
style={{ cursor: 'pointer' }} <td
onClick={() => handleEditLink(link)} style={{ cursor: 'pointer' }}
> onClick={() => handleEditLink(link)}
<FontAwesomeIcon >
icon={faPen} <FontAwesomeIcon
style={{ width: '1.5rem' }} icon={faPen}
/> style={{ width: '1.5rem' }}
</td> />
<td </td>
style={{ cursor: 'pointer' }} <td
onClick={() => handleRemoveLink(link.id)} style={{ cursor: 'pointer' }}
> onClick={() => handleRemoveLink(link.id)}
<FontAwesomeIcon >
icon={faTrash} <FontAwesomeIcon
style={{ width: '1.5rem' }} icon={faTrash}
/> style={{ width: '1.5rem' }}
</td> />
</> </td>
) </>
// Si c'est le dernier élément
if (linksData.rows.length === index + 1) {
return (
<tr key={index} ref={lastLinkRef}>
{linkJSX}
</tr>
) )
} // Si c'est le dernier élément
return <tr key={index}>{linkJSX}</tr> if (linksData.rows.length === index + 1) {
})} return (
</tbody> <tr key={index} ref={lastLinkRef}>
</table> {linkJSX}
</div> </tr>
) : ( )
<Modal> }
<div className='Admin__Modal__container container-fluid'> return <tr key={index}>{linkJSX}</tr>
<div className='Admin__Modal__row row'> })}
<div className='col-24'> </tbody>
<div className='Admin__Modal-top-container row'> </table>
<div className='col-24'> </div>
<span )
onClick={toggleModal} : (
style={{ <Modal>
cursor: 'pointer', <div className='Admin__Modal__container container-fluid'>
position: 'absolute', <div className='Admin__Modal__row row'>
left: 0 <div className='col-24'>
}} <div className='Admin__Modal-top-container row'>
> <div className='col-24'>
<FontAwesomeIcon <span
icon={faTimes} onClick={toggleModal}
style={{ width: '1.5rem', color: 'red' }} style={{
/> cursor: 'pointer',
</span> position: 'absolute',
left: 0
}}
>
<FontAwesomeIcon
icon={faTimes}
style={{ width: '1.5rem', color: 'red' }}
/>
</span>
</div>
</div> </div>
</div> </div>
</div>
<div className='col-24'> <div className='col-24'>
<form onSubmit={handleEditSubmit}> <form onSubmit={handleEditSubmit}>
<div className='form-group'> <div className='form-group'>
<label className='form-label' htmlFor='url'> <label className='form-label' htmlFor='url'>
Entrez le lien à raccourcir : Entrez le lien à raccourcir :
</label> </label>
<input <input
value={defaultInputState.url} value={defaultInputState.url}
onChange={handleChange} onChange={handleChange}
type='text' type='text'
name='url' name='url'
id='url' id='url'
placeholder='(e.g : https://divlo.fr)' placeholder='(e.g : https://divlo.fr)'
className='form-control' className='form-control'
/> />
</div> </div>
<div className='form-group'> <div className='form-group'>
<label className='form-label' htmlFor='shortcutName'> <label className='form-label' htmlFor='shortcutName'>
Entrez le nom du raccourci : Entrez le nom du raccourci :
</label> </label>
<input <input
value={defaultInputState.shortcutName} value={defaultInputState.shortcutName}
onChange={handleChange} onChange={handleChange}
type='text' type='text'
name='shortcutName' name='shortcutName'
id='shortcutName' id='shortcutName'
placeholder='(e.g : divlo)' placeholder='(e.g : divlo)'
className='form-control' className='form-control'
/> />
</div> </div>
<div className='form-group text-center'> <div className='form-group text-center'>
<button type='submit' className='btn btn-dark'> <button type='submit' className='btn btn-dark'>
Envoyer Envoyer
</button> </button>
</div>
</form>
<div className='form-result text-center'>
{isLoading ? <Loader /> : htmlParser(message)}
</div> </div>
</form>
<div className='form-result text-center'>
{isLoading ? <Loader /> : htmlParser(message)}
</div> </div>
</div> </div>
</div> </div>
</div> </Modal>
</Modal> )}
)}
</div> </div>
</div> </div>
</div> </div>

View File

@ -54,104 +54,110 @@ const PlayRightPrice = () => {
return ( return (
<div className='container-fluid'> <div className='container-fluid'>
{!isPlaying ? ( {!isPlaying
<div className='row justify-content-center'> ? (
<div className='form-group text-center'>
<button
onClick={handlePlaying}
type='submit'
className='btn btn-dark'
>
Jouer
</button>
</div>
</div>
) : isLoadingProduct ? (
<div className='row justify-content-center'>
<Loader />
</div>
) : (
<>
<div className='row justify-content-center'> <div className='row justify-content-center'>
<div <div className='form-group text-center'>
style={{ marginBottom: '20px' }} <button
className='col-24 text-center' onClick={handlePlaying}
> type='submit'
<h4>{productToGuess.name}</h4> className='btn btn-dark'
<img >
src={productToGuess.image} Jouer
alt={productToGuess.name} </button>
className='Product__image'
/>
</div> </div>
</div> </div>
)
<div className='row justify-content-center'> : isLoadingProduct
<div style={{ marginBottom: '25px' }} className='col-24'> ? (
{attemptsArray.length > 0 && <div className='row justify-content-center'>
attemptsArray[0].message === <Loader />
'Bravo, vous avez trouvé le juste prix !' ? (
<div className='form-group text-center'>
<button
onClick={handlePlaying}
type='submit'
className='btn btn-dark'
>
Rejouer ?
</button>
</div>
) : (
<form onSubmit={handleSubmit}>
<div className='text-center'>
<input
value={enteredPrice}
onChange={handleChange}
name='enteredPrice'
id='enteredPrice'
type='number'
step='0.01'
className='form-control'
autoComplete='off'
placeholder='Devinez le prix (prix à virgule possible!)'
/>
</div>
<div className='form-group text-center'>
<button type='submit' className='btn btn-dark'>
Deviner
</button>
</div>
</form>
)}
</div> </div>
</div> )
: (
<div <>
style={{ marginBottom: '30px' }} <div className='row justify-content-center'>
className='row justify-content-center'
>
{attemptsArray.map((attempt, index) => {
const { message } = attempt
let priceResultClass
if (message === "C'est moins !") {
priceResultClass = 'Price__result-moins'
} else if (message === "C'est plus !") {
priceResultClass = 'Price__result-plus'
} else {
priceResultClass = 'Price__result-success'
}
return (
<div <div
key={index} style={{ marginBottom: '20px' }}
className={`col-24 Price__result ${priceResultClass}`} className='col-24 text-center'
> >
# {attempt.numberTry} ({attempt.guessedPrice}) {message} <h4>{productToGuess.name}</h4>
<img
src={productToGuess.image}
alt={productToGuess.name}
className='Product__image'
/>
</div> </div>
) </div>
})}
</div> <div className='row justify-content-center'>
</> <div style={{ marginBottom: '25px' }} className='col-24'>
)} {attemptsArray.length > 0 &&
attemptsArray[0].message ===
'Bravo, vous avez trouvé le juste prix !'
? (
<div className='form-group text-center'>
<button
onClick={handlePlaying}
type='submit'
className='btn btn-dark'
>
Rejouer ?
</button>
</div>
)
: (
<form onSubmit={handleSubmit}>
<div className='text-center'>
<input
value={enteredPrice}
onChange={handleChange}
name='enteredPrice'
id='enteredPrice'
type='number'
step='0.01'
className='form-control'
autoComplete='off'
placeholder='Devinez le prix (prix à virgule possible!)'
/>
</div>
<div className='form-group text-center'>
<button type='submit' className='btn btn-dark'>
Deviner
</button>
</div>
</form>
)}
</div>
</div>
<div
style={{ marginBottom: '30px' }}
className='row justify-content-center'
>
{attemptsArray.map((attempt, index) => {
const { message } = attempt
let priceResultClass
if (message === "C'est moins !") {
priceResultClass = 'Price__result-moins'
} else if (message === "C'est plus !") {
priceResultClass = 'Price__result-plus'
} else {
priceResultClass = 'Price__result-success'
}
return (
<div
key={index}
className={`col-24 Price__result ${priceResultClass}`}
>
# {attempt.numberTry} ({attempt.guessedPrice}) {message}
</div>
)
})}
</div>
</>
)}
</div> </div>
) )
} }

View File

@ -84,299 +84,301 @@ const Profile = props => {
/> />
{/* Édition du profil */} {/* Édition du profil */}
{isOpen ? ( {isOpen
<Modal toggleModal={toggleModal}> ? (
<div className='Profile__container container-fluid'> <Modal toggleModal={toggleModal}>
<div className='Profile__row row'> <div className='Profile__container container-fluid'>
<div className='col-24'> <div className='Profile__row row'>
<div className='Profile__Modal-top-container row'> <div className='col-24'>
<div className='col-24'> <div className='Profile__Modal-top-container row'>
<span <div className='col-24'>
<span
onClick={toggleModal}
style={{
cursor: 'pointer',
position: 'absolute',
left: 0
}}
>
<FontAwesomeIcon
icon={faTimes}
style={{ width: '1.5rem', color: 'red' }}
/>
</span>
<h2 className='text-center'>Éditer le profil</h2>
<p className='text-center'>
<em>
(Vous devrez vous reconnecter après la sauvegarde){' '}
<br /> Si vous changez votre adresse email, vous devrez
la confirmer comme à l'inscription (vérifier vos
emails).
</em>
</p>
</div>
</div>
</div>
<div className='col-24'>
<form onSubmit={handleSubmit}>
<div className='form-group'>
<label className='form-label' htmlFor='name'>
Nom :
</label>
<input
value={inputState.name}
onChange={handleChange}
type='text'
name='name'
id='name'
className='form-control'
placeholder='Divlo'
/>
</div>
<div className='form-group'>
<label className='form-label' htmlFor='email'>
Email :
</label>
<input
value={inputState.email}
onChange={handleChange}
type='email'
name='email'
id='email'
className='form-control'
placeholder='email@gmail.com'
/>
</div>
<div className='form-group custom-control custom-switch'>
<input
onChange={event => handleChange(event, true)}
type='checkbox'
name='isPublicEmail'
checked={inputState.isPublicEmail}
className='custom-control-input'
id='isPublicEmail'
/>
<label
className='custom-control-label'
htmlFor='isPublicEmail'
>
Email Public
</label>
</div>
<div className='form-group'>
<label className='form-label' htmlFor='biography'>
Biographie :
</label>
<textarea
style={{ height: 'auto' }}
value={inputState.biography}
onChange={handleChange}
name='biography'
id='biography'
className='form-control'
rows='5'
/>
</div>
<div className='form-group'>
<label className='form-label' htmlFor='logo'>
Logo <em>(400x400 recommandé)</em> :
</label>
<p style={{ margin: 0 }}>
<em>
Si aucun fichier est choisi, le logo ne sera pas
modifié.
</em>
</p>
<input
onChange={handleChange}
accept='image/jpeg,image/jpg,image/png,image/gif'
type='file'
name='logo'
id='logo'
/>
</div>
<div className='form-group text-center'>
<button type='submit' className='btn btn-dark'>
Sauvegarder
</button>
</div>
</form>
<div className='form-result text-center'>
{isLoading ? <Loader /> : htmlParser(message)}
</div>
</div>
</div>
</div>
</Modal>
)
: (
<div className='container-fluid Profile__container'>
<div className='row Profile__row'>
<div className='col-20'>
<div className='text-center'>
<h1>
Profil de <span className='important'>{props.name}</span>
</h1>
</div>
<div className='row justify-content-center'>
<div className='col-24 text-center'>
<img
className='Profile__logo'
src={API_URL + props.logo}
alt={props.name}
/>
</div>
<div className='col-24 text-center'>
{props.biography != null && <p>{props.biography}</p>}
{props.email != null && (
<p>
<span className='important'>Email :</span> {props.email}
</p>
)}
<p>
<span className='important'>
Date de création du compte :
</span>{' '}
{date.format(
new Date(props.createdAt),
'DD/MM/YYYY à HH:mm',
false
)}
</p>
</div>
{isAuth && user.name === props.name && (
<button
onClick={toggleModal} onClick={toggleModal}
style={{ style={{ marginBottom: '25px' }}
cursor: 'pointer', className='btn btn-dark'
position: 'absolute',
left: 0
}}
> >
<FontAwesomeIcon <FontAwesomeIcon
icon={faTimes} icon={faPen}
style={{ width: '1.5rem', color: 'red' }} style={{ cursor: 'pointer', width: '1rem' }}
/> />
</span> &nbsp; Éditez le profil
<h2 className='text-center'>Éditer le profil</h2>
<p className='text-center'>
<em>
(Vous devrez vous reconnecter après la sauvegarde){' '}
<br /> Si vous changez votre adresse email, vous devrez
la confirmer comme à l'inscription (vérifier vos
emails).
</em>
</p>
</div>
</div>
</div>
<div className='col-24'>
<form onSubmit={handleSubmit}>
<div className='form-group'>
<label className='form-label' htmlFor='name'>
Nom :
</label>
<input
value={inputState.name}
onChange={handleChange}
type='text'
name='name'
id='name'
className='form-control'
placeholder='Divlo'
/>
</div>
<div className='form-group'>
<label className='form-label' htmlFor='email'>
Email :
</label>
<input
value={inputState.email}
onChange={handleChange}
type='email'
name='email'
id='email'
className='form-control'
placeholder='email@gmail.com'
/>
</div>
<div className='form-group custom-control custom-switch'>
<input
onChange={event => handleChange(event, true)}
type='checkbox'
name='isPublicEmail'
checked={inputState.isPublicEmail}
className='custom-control-input'
id='isPublicEmail'
/>
<label
className='custom-control-label'
htmlFor='isPublicEmail'
>
Email Public
</label>
</div>
<div className='form-group'>
<label className='form-label' htmlFor='biography'>
Biographie :
</label>
<textarea
style={{ height: 'auto' }}
value={inputState.biography}
onChange={handleChange}
name='biography'
id='biography'
className='form-control'
rows='5'
/>
</div>
<div className='form-group'>
<label className='form-label' htmlFor='logo'>
Logo <em>(400x400 recommandé)</em> :
</label>
<p style={{ margin: 0 }}>
<em>
Si aucun fichier est choisi, le logo ne sera pas
modifié.
</em>
</p>
<input
onChange={handleChange}
accept='image/jpeg,image/jpg,image/png,image/gif'
type='file'
name='logo'
id='logo'
/>
</div>
<div className='form-group text-center'>
<button type='submit' className='btn btn-dark'>
Sauvegarder
</button> </button>
</div> )}
</form>
<div className='form-result text-center'>
{isLoading ? <Loader /> : htmlParser(message)}
</div> </div>
</div> </div>
</div> </div>
</div>
</Modal> {props.favoritesArray.length > 0 && (
) : (
<div className='container-fluid Profile__container'>
<div className='row Profile__row'>
<div className='col-20'>
<div className='text-center'>
<h1>
Profil de <span className='important'>{props.name}</span>
</h1>
</div>
<div className='row justify-content-center'> <div className='row justify-content-center'>
<div className='col-24 text-center'> <div className='col-24 text-center'>
<img <h2>
className='Profile__logo' Dernières fonctions ajoutées aux{' '}
src={API_URL + props.logo} <span className='important'>favoris</span> :
alt={props.name} </h2>
/>
</div> </div>
<div className='col-24'>
<div className='row justify-content-center'>
{props.favoritesArray.map(favorite => {
return <FunctionCard key={favorite.id} {...favorite} />
})}
</div>
</div>
</div>
)}
{props.commentsArray.length > 0 && (
<div className='row justify-content-center'>
<div className='col-24 text-center'> <div className='col-24 text-center'>
{props.biography != null && <p>{props.biography}</p>} <h2>
{props.email != null && ( Derniers <span className='important'>commentaires</span> :
<p> </h2>
<span className='important'>Email :</span> {props.email}
</p>
)}
<p>
<span className='important'>
Date de création du compte :
</span>{' '}
{date.format(
new Date(props.createdAt),
'DD/MM/YYYY à HH:mm',
false
)}
</p>
</div> </div>
<div className='col-24'>
{isAuth && user.name === props.name && ( {props.commentsArray.map(comment => (
<button <div
onClick={toggleModal} key={comment.id}
style={{ marginBottom: '25px' }} className='row Profile__row Profile__comment'
className='btn btn-dark' >
> <div className='col-20'>
<FontAwesomeIcon <p style={{ textAlign: 'center', marginTop: '30px' }}>
icon={faPen} Posté sur la fonction&nbsp;
style={{ cursor: 'pointer', width: '1rem' }} <Link
/> href={
&nbsp; Éditez le profil
</button>
)}
</div>
</div>
</div>
{props.favoritesArray.length > 0 && (
<div className='row justify-content-center'>
<div className='col-24 text-center'>
<h2>
Dernières fonctions ajoutées aux{' '}
<span className='important'>favoris</span> :
</h2>
</div>
<div className='col-24'>
<div className='row justify-content-center'>
{props.favoritesArray.map(favorite => {
return <FunctionCard key={favorite.id} {...favorite} />
})}
</div>
</div>
</div>
)}
{props.commentsArray.length > 0 && (
<div className='row justify-content-center'>
<div className='col-24 text-center'>
<h2>
Derniers <span className='important'>commentaires</span> :
</h2>
</div>
<div className='col-24'>
{props.commentsArray.map(comment => (
<div
key={comment.id}
className='row Profile__row Profile__comment'
>
<div className='col-20'>
<p style={{ textAlign: 'center', marginTop: '30px' }}>
Posté sur la fonction&nbsp;
<Link
href={
comment.function.type === 'form' || comment.function.type === 'form' ||
comment.function.type === 'article' comment.function.type === 'article'
? '/functions/[slug]' ? '/functions/[slug]'
: `/functions/${comment.function.slug}` : `/functions/${comment.function.slug}`
} }
as={`/functions/${comment.function.slug}`} as={`/functions/${comment.function.slug}`}
> >
<a>{comment.function.title}</a> <a>{comment.function.title}</a>
</Link> </Link>
&nbsp;le{' '} &nbsp;le{' '}
{date.format( {date.format(
new Date(comment.createdAt), new Date(comment.createdAt),
'DD/MM/YYYY à HH:mm', 'DD/MM/YYYY à HH:mm',
false false
)} )}
</p> </p>
<ReactMarkdown <ReactMarkdown
source={comment.message} source={comment.message}
renderers={{ code: CodeBlock }} renderers={{ code: CodeBlock }}
/> />
</div>
</div> </div>
</div> ))}
))} </div>
</div> </div>
</div> )}
)}
{props.quotesArray.length > 0 && ( {props.quotesArray.length > 0 && (
<div className='row justify-content-center'> <div className='row justify-content-center'>
<div className='col-24 text-center'> <div className='col-24 text-center'>
<h2> <h2>
Dernières <span className='important'>citations</span>{' '} Dernières <span className='important'>citations</span>{' '}
proposées (et validées) : proposées (et validées) :
</h2> </h2>
<p> <p>
Citations pour la fonction{' '} Citations pour la fonction{' '}
<Link href='/functions/randomQuote'> <Link href='/functions/randomQuote'>
<a>Générateur de citations</a> <a>Générateur de citations</a>
</Link> </Link>
. .
</p> </p>
</div>
<div className='col-24 table-column'>
<table style={{ marginBottom: '50px' }}>
<thead>
<tr>
<th className='table-row' scope='col'>
Citation/Proverbe
</th>
<th className='table-row' scope='col'>
Auteur
</th>
</tr>
</thead>
<tbody>
{props.quotesArray.map((currentQuote, index) => {
return (
<tr key={index}>
<td className='table-row text-center'>
{currentQuote.quote}
</td>
<td className='table-row text-center'>
{currentQuote.author}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div> </div>
<div className='col-24 table-column'> )}
<table style={{ marginBottom: '50px' }}> </div>
<thead>
<tr>
<th className='table-row' scope='col'>
Citation/Proverbe
</th>
<th className='table-row' scope='col'>
Auteur
</th>
</tr>
</thead>
<tbody>
{props.quotesArray.map((currentQuote, index) => {
return (
<tr key={index}>
<td className='table-row text-center'>
{currentQuote.quote}
</td>
<td className='table-row text-center'>
{currentQuote.author}
</td>
</tr>
)
})}
</tbody>
</table>
</div>
</div>
)} )}
</div>
)}
</> </>
) )
} }

View File

@ -2,11 +2,15 @@ import axios from 'axios'
export const API_URL = process.env.NEXT_PUBLIC_API_URL export const API_URL = process.env.NEXT_PUBLIC_API_URL
const api = axios.create({ const api = (() => {
baseURL: API_URL, const baseURL =
headers: { typeof window === 'undefined' ? process.env.CONTAINER_API_URL : API_URL
'Content-Type': 'application/json' return axios.create({
} baseURL,
}) headers: {
'Content-Type': 'application/json'
}
})
})()
export default api export default api