commit
cbe82f74a9
28
README.md
28
README.md
@ -5,10 +5,9 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://gitmoji.carloscuesta.me/"><img src="https://camo.githubusercontent.com/2a4924a23bd9ef18afe793f4999b1b9ec474e48f/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6769746d6f6a692d253230f09f989c253230f09f988d2d4646444436372e7376673f7374796c653d666c61742d737175617265" alt="Gitmoji"/></a>
|
|
||||||
<a href="https://standardjs.com"><img alt="JavaScript Style Guide" src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"/></a>
|
<a href="https://standardjs.com"><img alt="JavaScript Style Guide" src="https://img.shields.io/badge/code_style-standard-brightgreen.svg"/></a>
|
||||||
|
<a href="https://conventionalcommits.org"><img src="https://img.shields.io/badge/Conventional%20Commits-1.0.0-yellow.svg" alt="Conventional Commits" /></a>
|
||||||
<a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a>
|
<a href="./LICENSE"><img src="https://img.shields.io/badge/licence-MIT-blue.svg" alt="Licence MIT"/></a>
|
||||||
<img src="https://img.shields.io/github/stars/Divlo/FunctionProject?style=social" alt="Stars">
|
|
||||||
<br/> <br/>
|
<br/> <br/>
|
||||||
<a href="https://function.divlo.fr/"><img src="https://raw.githubusercontent.com/Divlo/FunctionProject/master/.github/FunctionProject.png" alt="FunctionProject" /></a>
|
<a href="https://function.divlo.fr/"><img src="https://raw.githubusercontent.com/Divlo/FunctionProject/master/.github/FunctionProject.png" alt="FunctionProject" /></a>
|
||||||
</p>
|
</p>
|
||||||
@ -23,7 +22,7 @@ Si vous aimez le projet, vous pouvez aider à **le faire connaître** en utilisa
|
|||||||
|
|
||||||
Les dernières versions publiées : [https://github.com/Divlo/FunctionProject/releases](https://github.com/Divlo/FunctionProject/releases)
|
Les dernières versions publiées : [https://github.com/Divlo/FunctionProject/releases](https://github.com/Divlo/FunctionProject/releases)
|
||||||
|
|
||||||
Le projet est disponible sur [function.divlo.fr](https://function.divlo.fr/) (actuellement en version 2.1).
|
Le projet est disponible sur [function.divlo.fr](https://function.divlo.fr/) (actuellement en version 2.2).
|
||||||
|
|
||||||
## 🚀 Open Source
|
## 🚀 Open Source
|
||||||
|
|
||||||
@ -39,8 +38,9 @@ Si vous voulez avoir les données des catégories et des fonctions, vous pouvez
|
|||||||
|
|
||||||
### Prérequis :
|
### Prérequis :
|
||||||
|
|
||||||
- NodeJS (et npm) → version récente
|
- [Node.js](https://nodejs.org/) >= 14
|
||||||
- Base de donnée MySQL → J'utilise Wamp ce qui me permet d'avoir phpmyadmin.
|
- [npm](https://www.npmjs.com/) >= 6
|
||||||
|
- [MySQL](https://www.mysql.com/) >= 5.7
|
||||||
|
|
||||||
### Commandes (à suivre dans l'ordre) :
|
### Commandes (à suivre dans l'ordre) :
|
||||||
|
|
||||||
@ -62,6 +62,24 @@ Vous devrez ensuite configurer l'API en créant un fichier `.env` à la racine d
|
|||||||
|
|
||||||
### Lancer l'environnement de développement :
|
### Lancer l'environnement de développement :
|
||||||
|
|
||||||
|
#### Avec [docker](https://www.docker.com/) :
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Setup and run all the services for you
|
||||||
|
docker-compose up --build
|
||||||
|
```
|
||||||
|
|
||||||
|
**Services started :**
|
||||||
|
|
||||||
|
- api : `http://localhost:8080`
|
||||||
|
- s.divlo.fr : `http://localhost:7000`
|
||||||
|
- website : `http://localhost:3000`
|
||||||
|
- [phpmyadmin](https://www.phpmyadmin.net/) : `http://localhost:8000`
|
||||||
|
- [MailDev](https://maildev.github.io/maildev/) : `http://localhost:1080`
|
||||||
|
- [MySQL database](https://www.mysql.com/) (with PORT 3006)
|
||||||
|
|
||||||
|
#### Sans docker :
|
||||||
|
|
||||||
Dans deux terminals séparés :
|
Dans deux terminals séparés :
|
||||||
|
|
||||||
- Lancer le front-end en allant dans `/website`
|
- Lancer le front-end en allant dans `/website`
|
||||||
|
2
api/.dockerignore
Normal file
2
api/.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
@ -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
6
api/.gitignore
vendored
@ -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
13
api/Dockerfile
Normal 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
|
24
api/app.js
24
api/app.js
@ -6,6 +6,7 @@ const helmet = require('helmet')
|
|||||||
const cors = require('cors')
|
const cors = require('cors')
|
||||||
const morgan = require('morgan')
|
const morgan = require('morgan')
|
||||||
const { redirectToHTTPS } = require('express-http-to-https')
|
const { redirectToHTTPS } = require('express-http-to-https')
|
||||||
|
const rateLimit = require('express-rate-limit')
|
||||||
|
|
||||||
/* Files Imports & Variables */
|
/* Files Imports & Variables */
|
||||||
const sequelize = require('./assets/utils/database')
|
const sequelize = require('./assets/utils/database')
|
||||||
@ -16,11 +17,27 @@ 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())
|
||||||
|
const requestPerSecond = 2
|
||||||
|
const seconds = 60
|
||||||
|
const windowMs = seconds * 1000
|
||||||
|
app.enable('trust proxy')
|
||||||
|
app.use(
|
||||||
|
rateLimit({
|
||||||
|
windowMs,
|
||||||
|
max: seconds * requestPerSecond,
|
||||||
|
handler: (_req, res) => {
|
||||||
|
return res.status(429).json({ message: 'Too many requests' })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
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 +54,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 +100,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(() => {
|
||||||
|
@ -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'
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
1656
api/package-lock.json
generated
1656
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,37 +1,38 @@
|
|||||||
{
|
{
|
||||||
"name": "api",
|
"name": "api",
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"description": "Backend REST API for FunctionProject",
|
"description": "Backend REST API for FunctionProject",
|
||||||
"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-fileupload": "^1.1.6",
|
|
||||||
"express-http-to-https": "^1.1.4",
|
|
||||||
"express-validator": "^6.4.0",
|
|
||||||
"helmet": "^3.21.3",
|
|
||||||
"jsdom": "^16.2.2",
|
|
||||||
"jsonwebtoken": "^8.5.1",
|
|
||||||
"moment": "^2.24.0",
|
|
||||||
"ms": "^2.1.2",
|
|
||||||
"mysql2": "^2.1.0",
|
|
||||||
"nodemailer": "^6.4.6",
|
|
||||||
"sequelize": "^5.21.5",
|
|
||||||
"smart-request-balancer": "^2.1.1",
|
|
||||||
"uuid": "^7.0.2",
|
|
||||||
"validator": "^13.0.0",
|
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"morgan": "^1.9.1"
|
"express": "^4.17.1",
|
||||||
|
"express-fileupload": "^1.2.0",
|
||||||
|
"express-http-to-https": "^1.1.4",
|
||||||
|
"express-rate-limit": "^5.1.3",
|
||||||
|
"express-validator": "^6.6.1",
|
||||||
|
"helmet": "^4.1.1",
|
||||||
|
"jsdom": "^16.4.0",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"moment": "^2.29.1",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"ms": "^2.1.2",
|
||||||
|
"mysql2": "^2.2.5",
|
||||||
|
"nodemailer": "^6.4.14",
|
||||||
|
"sequelize": "^6.3.5",
|
||||||
|
"smart-request-balancer": "^2.1.1",
|
||||||
|
"uuid": "^8.3.1",
|
||||||
|
"validator": "^13.1.17"
|
||||||
},
|
},
|
||||||
"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
73
docker-compose.yml
Normal 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
2
s.divlo.fr/.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
@ -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
13
s.divlo.fr/Dockerfile
Normal 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
|
@ -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}.`)
|
||||||
})
|
})
|
||||||
|
1707
s.divlo.fr/package-lock.json
generated
1707
s.divlo.fr/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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
2
website/.dockerignore
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
build
|
@ -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
9
website/Dockerfile
Normal 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"]
|
@ -8,7 +8,7 @@ export default function Footer () {
|
|||||||
<Link href='/about'>
|
<Link href='/about'>
|
||||||
<a>FunctionProject</a>
|
<a>FunctionProject</a>
|
||||||
</Link>
|
</Link>
|
||||||
- Version 2.1 <br />
|
- Version 2.2 <br />
|
||||||
<a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'>
|
<a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'>
|
||||||
Divlo
|
Divlo
|
||||||
</a>{' '}
|
</a>{' '}
|
||||||
|
@ -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
|
||||||
|
@ -82,62 +82,68 @@ const CommentCard = forwardRef((props, ref) => {
|
|||||||
<a>{props.user.name}</a>
|
<a>{props.user.name}</a>
|
||||||
</Link>
|
</Link>
|
||||||
-{' '}
|
-{' '}
|
||||||
{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>
|
||||||
-
|
-
|
||||||
<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>
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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' />
|
||||||
)}
|
)}
|
||||||
|
4270
website/package-lock.json
generated
4270
website/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,47 +1,49 @@
|
|||||||
{
|
{
|
||||||
"name": "website",
|
"name": "website",
|
||||||
"version": "2.1.0",
|
"version": "2.2.0",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
)}
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
É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
|
||||||
style={{ cursor: 'pointer', width: '1rem' }}
|
<Link
|
||||||
/>
|
href={
|
||||||
É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
|
|
||||||
<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>
|
||||||
le{' '}
|
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>
|
|
||||||
)}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
Reference in New Issue
Block a user