commit
cbe82f74a9
2
.github/backup.sql
vendored
2
.github/backup.sql
vendored
File diff suppressed because one or more lines are too long
2
.gitignore
vendored
2
.gitignore
vendored
@ -1 +1 @@
|
||||
.github/backup
|
||||
.github/backup
|
||||
|
2
LICENSE
2
LICENSE
@ -5,4 +5,4 @@ Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
28
README.md
28
README.md
@ -5,10 +5,9 @@
|
||||
</p>
|
||||
|
||||
<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://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>
|
||||
<img src="https://img.shields.io/github/stars/Divlo/FunctionProject?style=social" alt="Stars">
|
||||
<br/> <br/>
|
||||
<a href="https://function.divlo.fr/"><img src="https://raw.githubusercontent.com/Divlo/FunctionProject/master/.github/FunctionProject.png" alt="FunctionProject" /></a>
|
||||
</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)
|
||||
|
||||
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
|
||||
|
||||
@ -39,8 +38,9 @@ Si vous voulez avoir les données des catégories et des fonctions, vous pouvez
|
||||
|
||||
### Prérequis :
|
||||
|
||||
- NodeJS (et npm) → version récente
|
||||
- Base de donnée MySQL → J'utilise Wamp ce qui me permet d'avoir phpmyadmin.
|
||||
- [Node.js](https://nodejs.org/) >= 14
|
||||
- [npm](https://www.npmjs.com/) >= 6
|
||||
- [MySQL](https://www.mysql.com/) >= 5.7
|
||||
|
||||
### 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 :
|
||||
|
||||
#### 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 :
|
||||
|
||||
- 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"
|
||||
FRONT_END_HOST = "http://localhost:3000"
|
||||
OpenWeatherMap_API_KEY = ""
|
||||
Scraper_API_KEY = ""
|
||||
DB_HOST = ""
|
||||
DB_NAME = ""
|
||||
DB_USER = ""
|
||||
DB_PASS = ""
|
||||
JWT_SECRET = ""
|
||||
EMAIL_HOST = ""
|
||||
EMAIL_USER = ""
|
||||
EMAIL_PASSWORD = ""
|
||||
HOST="http://localhost:8080"
|
||||
FRONT_END_HOST="http://localhost:3000"
|
||||
OpenWeatherMap_API_KEY=""
|
||||
Scraper_API_KEY=""
|
||||
DATABASE_HOST="functionproject-database"
|
||||
DATABASE_NAME="functionproject"
|
||||
DATABASE_USER="root"
|
||||
DATABASE_PASSWORD="password"
|
||||
DATABASE_PORT=3306
|
||||
JWT_SECRET=""
|
||||
EMAIL_HOST="functionproject-maildev"
|
||||
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
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
# envs
|
||||
.env
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.production
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
/temp
|
||||
/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 morgan = require('morgan')
|
||||
const { redirectToHTTPS } = require('express-http-to-https')
|
||||
const rateLimit = require('express-rate-limit')
|
||||
|
||||
/* Files Imports & Variables */
|
||||
const sequelize = require('./assets/utils/database')
|
||||
@ -16,11 +17,27 @@ const isAdmin = require('./middlewares/isAdmin')
|
||||
const app = express()
|
||||
|
||||
/* 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(cors())
|
||||
app.use(morgan('dev'))
|
||||
app.use(express.json())
|
||||
app.use(redirectToHTTPS([/localhost:(\d{4})/]))
|
||||
|
||||
/* Routes */
|
||||
app.use('/images', express.static(path.join(__dirname, 'assets', 'images')))
|
||||
@ -37,7 +54,7 @@ app.use('/links', require('./routes/links_shortener'))
|
||||
/* Errors Handling */
|
||||
app.use((_req, _res, next) =>
|
||||
errorHandling(next, { statusCode: 404, message: "La route n'existe pas!" })
|
||||
) // 404
|
||||
)
|
||||
app.use((error, _req, res, _next) => {
|
||||
console.log(error)
|
||||
const { statusCode, message } = error
|
||||
@ -83,7 +100,6 @@ Users.hasMany(ShortLinks)
|
||||
ShortLinks.belongsTo(Users, { constraints: false })
|
||||
|
||||
/* Server */
|
||||
// sequelize.sync({ force: true })
|
||||
sequelize
|
||||
.sync()
|
||||
.then(() => {
|
||||
|
@ -1,3 +1,8 @@
|
||||
const dotenv = require('dotenv')
|
||||
|
||||
dotenv.config()
|
||||
const EMAIL_PORT = parseInt(process.env.EMAIL_PORT ?? '465', 10)
|
||||
|
||||
const config = {
|
||||
PORT: process.env.PORT || 8080,
|
||||
HOST: process.env.HOST,
|
||||
@ -5,20 +10,22 @@ const config = {
|
||||
WEATHER_API_KEY: process.env.OpenWeatherMap_API_KEY,
|
||||
SCRAPER_API_KEY: process.env.Scraper_API_KEY,
|
||||
DATABASE: {
|
||||
host: process.env.DB_HOST,
|
||||
name: process.env.DB_NAME,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS
|
||||
host: process.env.DATABASE_HOST,
|
||||
name: process.env.DATABASE_NAME,
|
||||
user: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASSWORD,
|
||||
port: parseInt(process.env.DATABASE_PORT ?? '3306', 10)
|
||||
},
|
||||
JWT_SECRET: process.env.JWT_SECRET,
|
||||
EMAIL_INFO: {
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: 465,
|
||||
secure: true, // true for 465, false for other ports
|
||||
port: EMAIL_PORT,
|
||||
secure: EMAIL_PORT === 465,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASSWORD
|
||||
}
|
||||
},
|
||||
ignoreTLS: process.env.NODE_ENV !== 'production'
|
||||
},
|
||||
TOKEN_LIFE: '1 week'
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ const sequelize = new Sequelize(
|
||||
DATABASE.password,
|
||||
{
|
||||
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",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0",
|
||||
"description": "Backend REST API for FunctionProject",
|
||||
"scripts": {
|
||||
"start": "node app.js",
|
||||
"dev": "nodemon app.js",
|
||||
"format": "standard \"./**/*.js\" --fix | snazzy || exit 0"
|
||||
"format": "standard \"./**/*.js\" --fix | snazzy"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.19.2",
|
||||
"axios": "^0.21.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"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",
|
||||
"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": {
|
||||
"nodemon": "^2.0.4",
|
||||
"snazzy": "^8.0.0",
|
||||
"standard": "^14.3.4"
|
||||
"nodemon": "^2.0.6",
|
||||
"snazzy": "^9.0.0",
|
||||
"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 = ""
|
||||
DB_NAME = ""
|
||||
DB_USER = ""
|
||||
DB_PASS = ""
|
||||
DATABASE_HOST="functionproject-database"
|
||||
DATABASE_NAME="functionproject"
|
||||
DATABASE_USER="root"
|
||||
DATABASE_PASSWORD="password"
|
||||
DATABASE_PORT=3306
|
||||
COMPOSE_PROJECT_NAME="s.divlo.fr-website"
|
||||
|
2
s.divlo.fr/.gitignore
vendored
2
s.divlo.fr/.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
node_modules
|
||||
.env
|
||||
.env
|
||||
|
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 */
|
||||
const app = express()
|
||||
const database = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASS,
|
||||
database: process.env.DB_NAME,
|
||||
port: process.env.DB_PORT
|
||||
host: process.env.DATABASE_HOST,
|
||||
user: process.env.DATABASE_USER,
|
||||
password: process.env.DATABASE_PASS,
|
||||
database: process.env.DATABASE_NAME,
|
||||
port: process.env.DATABASE_PORT
|
||||
})
|
||||
|
||||
/* 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(morgan('dev'))
|
||||
app.use(express.json())
|
||||
app.use(redirectToHTTPS([/localhost:(\d{4})/]))
|
||||
|
||||
/* EJS Template Engines */
|
||||
app.set('view engine', 'ejs')
|
||||
@ -77,7 +80,7 @@ app.use((error, _req, res) => {
|
||||
})
|
||||
|
||||
/* Server */
|
||||
const PORT = process.env.PORT || 8000
|
||||
const PORT = process.env.PORT || 7000
|
||||
app.listen(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": {
|
||||
"dotenv": "^8.2.0",
|
||||
"ejs": "^3.1.3",
|
||||
"ejs": "^3.1.5",
|
||||
"express": "^4.17.1",
|
||||
"express-http-to-https": "^1.1.4",
|
||||
"helmet": "^4.0.0",
|
||||
"helmet": "^4.1.1",
|
||||
"morgan": "^1.10.0",
|
||||
"mysql": "^2.18.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.4",
|
||||
"snazzy": "^8.0.0",
|
||||
"standard": "^14.3.4"
|
||||
"nodemon": "^2.0.6",
|
||||
"snazzy": "^9.0.0",
|
||||
"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'>
|
||||
<a>FunctionProject</a>
|
||||
</Link>
|
||||
- Version 2.1 <br />
|
||||
- Version 2.2 <br />
|
||||
<a href='https://divlo.fr/' target='_blank' rel='noopener noreferrer'>
|
||||
Divlo
|
||||
</a>{' '}
|
||||
|
@ -23,15 +23,15 @@ const FunctionCard = memo(
|
||||
<Link
|
||||
{...(props.isAdmin
|
||||
? {
|
||||
href: '/admin/[slug]',
|
||||
as: `/admin/${props.slug}`
|
||||
}
|
||||
href: '/admin/[slug]',
|
||||
as: `/admin/${props.slug}`
|
||||
}
|
||||
: {
|
||||
href: isFormOrArticle
|
||||
? '/functions/[slug]'
|
||||
: `/functions/${props.slug}`,
|
||||
as: `/functions/${props.slug}`
|
||||
})}
|
||||
href: isFormOrArticle
|
||||
? '/functions/[slug]'
|
||||
: `/functions/${props.slug}`,
|
||||
as: `/functions/${props.slug}`
|
||||
})}
|
||||
>
|
||||
{/* FunctionCard a une hauteur pendant chargement */}
|
||||
<a
|
||||
|
@ -82,62 +82,68 @@ const CommentCard = forwardRef((props, ref) => {
|
||||
<a>{props.user.name}</a>
|
||||
</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>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col-24'>
|
||||
{!isEditing ? (
|
||||
<>
|
||||
<div className='CommentCard__message'>
|
||||
<ReactMarkdown
|
||||
source={editInput}
|
||||
renderers={{ code: CodeBlock }}
|
||||
/>
|
||||
</div>
|
||||
{isAuth && user.name === props.user.name && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
margin: '15px 0 0 0',
|
||||
fontStyle: 'italic'
|
||||
}}
|
||||
>
|
||||
<a onClick={deleteCommentById} href='#'>
|
||||
supprimer
|
||||
</a>
|
||||
{!isEditing
|
||||
? (
|
||||
<>
|
||||
<div className='CommentCard__message'>
|
||||
<ReactMarkdown
|
||||
source={editInput}
|
||||
renderers={{ code: CodeBlock }}
|
||||
/>
|
||||
</div>
|
||||
{isAuth && user.name === props.user.name && (
|
||||
<p
|
||||
style={{
|
||||
fontSize: '15px',
|
||||
margin: '15px 0 0 0',
|
||||
fontStyle: 'italic'
|
||||
}}
|
||||
>
|
||||
<a onClick={deleteCommentById} href='#'>
|
||||
supprimer
|
||||
</a>
|
||||
-
|
||||
<a style={{ cursor: 'pointer' }} onClick={editComment}>
|
||||
modifier
|
||||
</a>
|
||||
</p>
|
||||
<a style={{ cursor: 'pointer' }} onClick={editComment}>
|
||||
modifier
|
||||
</a>
|
||||
</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>
|
||||
|
@ -3,11 +3,13 @@ import htmlParser from 'html-react-parser'
|
||||
const FunctionArticle = ({ article }) => {
|
||||
return (
|
||||
<div style={{ marginBottom: '50px' }} className='container-fluid'>
|
||||
{article != null ? (
|
||||
htmlParser(article)
|
||||
) : (
|
||||
<p className='text-center'>L'article n'est pas encore disponible.</p>
|
||||
)}
|
||||
{article != null
|
||||
? (
|
||||
htmlParser(article)
|
||||
)
|
||||
: (
|
||||
<p className='text-center'>L'article n'est pas encore disponible.</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -90,36 +90,38 @@ const FunctionComments = ({ functionId }) => {
|
||||
<div className='FunctionComments__post container-fluid'>
|
||||
<div className='row FunctionComments__row'>
|
||||
<div className='col-24'>
|
||||
{isAuth ? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className='form-group FunctionComments__post-group'>
|
||||
<label className='form-label' htmlFor='commentPost'>
|
||||
Ajouter un commentaire :
|
||||
</label>
|
||||
<textarea
|
||||
className='FunctionComments__textarea form-control'
|
||||
value={inputState.commentPost}
|
||||
onChange={handleChange}
|
||||
name='commentPost'
|
||||
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' }}>
|
||||
<button type='submit' className='btn btn-dark'>
|
||||
Envoyer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<p className='text-center'>
|
||||
Vous devez être{' '}
|
||||
<Link href='/users/login'>
|
||||
<a>connecté</a>
|
||||
</Link>{' '}
|
||||
pour poster un commentaire.
|
||||
</p>
|
||||
)}
|
||||
{isAuth
|
||||
? (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className='form-group FunctionComments__post-group'>
|
||||
<label className='form-label' htmlFor='commentPost'>
|
||||
Ajouter un commentaire :
|
||||
</label>
|
||||
<textarea
|
||||
className='FunctionComments__textarea form-control'
|
||||
value={inputState.commentPost}
|
||||
onChange={handleChange}
|
||||
name='commentPost'
|
||||
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' }}>
|
||||
<button type='submit' className='btn btn-dark'>
|
||||
Envoyer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)
|
||||
: (
|
||||
<p className='text-center'>
|
||||
Vous devez être{' '}
|
||||
<Link href='/users/login'>
|
||||
<a>connecté</a>
|
||||
</Link>{' '}
|
||||
pour poster un commentaire.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -51,35 +51,37 @@ export default function Header () {
|
||||
<NavigationLink name='Accueil' path='/' />
|
||||
<NavigationLink name='Fonctions' path='/functions' />
|
||||
<NavigationLink name='Utilisateurs' path='/users' />
|
||||
{!isAuth ? (
|
||||
<>
|
||||
<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
|
||||
className={`navbar-link ${
|
||||
{!isAuth
|
||||
? (
|
||||
<>
|
||||
<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
|
||||
className={`navbar-link ${
|
||||
pathname === '/users/[name]'
|
||||
? 'navbar-link-active'
|
||||
: null
|
||||
}`}
|
||||
>
|
||||
Mon Profil
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li className='navbar-item'>
|
||||
<Link href='/'>
|
||||
<a onClick={logoutUser} className='navbar-link'>
|
||||
Se déconnecter
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
>
|
||||
Mon Profil
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
<li className='navbar-item'>
|
||||
<Link href='/'>
|
||||
<a onClick={logoutUser} className='navbar-link'>
|
||||
Se déconnecter
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
{isAuth && user.isAdmin && (
|
||||
<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",
|
||||
"version": "2.1.0",
|
||||
"version": "2.2.0",
|
||||
"description": "Website frontend for FunctionProject",
|
||||
"main": "server.js",
|
||||
"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",
|
||||
"export": "next export",
|
||||
"start": "cross-env NODE_ENV=production node server",
|
||||
"format": "standard \"./**/*.{js,jsx}\" --fix | snazzy || exit 0"
|
||||
"format": "standard \"./**/*.{js,jsx}\" --fix | snazzy"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.28",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.13.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.13.0",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.13.0",
|
||||
"@fortawesome/react-fontawesome": "^0.1.9",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.32",
|
||||
"@fortawesome/free-brands-svg-icons": "^5.15.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^5.15.1",
|
||||
"@fortawesome/free-solid-svg-icons": "^5.15.1",
|
||||
"@fortawesome/react-fontawesome": "^0.1.12",
|
||||
"@zeit/next-css": "^1.0.1",
|
||||
"axios": "^0.19.2",
|
||||
"date-and-time": "^0.13.1",
|
||||
"date-fns": "^2.12.0",
|
||||
"axios": "^0.21.0",
|
||||
"date-and-time": "^0.14.1",
|
||||
"date-fns": "^2.16.1",
|
||||
"express": "^4.17.1",
|
||||
"express-http-to-https": "^1.1.4",
|
||||
"html-react-parser": "^0.10.2",
|
||||
"next": "^9.5.4",
|
||||
"next-fonts": "^1.0.3",
|
||||
"notyf": "^3.6.0",
|
||||
"html-react-parser": "^0.14.0",
|
||||
"next": "^10.0.0",
|
||||
"next-fonts": "^1.4.0",
|
||||
"notyf": "^3.9.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"react": "16.13.0",
|
||||
"react": "17.0.1",
|
||||
"react-codepen-embed": "^1.0.1",
|
||||
"react-color": "^2.18.0",
|
||||
"react-datepicker": "^2.14.1",
|
||||
"react-dom": "16.13.0",
|
||||
"react-markdown": "^4.3.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-datepicker": "^3.3.0",
|
||||
"react-dom": "17.0.1",
|
||||
"react-markdown": "^5.0.2",
|
||||
"react-swipeable-views": "^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",
|
||||
"universal-cookie": "^4.0.3"
|
||||
"universal-cookie": "^4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"cross-env": "^7.0.2",
|
||||
"snazzy": "^8.0.0",
|
||||
"standard": "^14.3.4"
|
||||
"snazzy": "^9.0.0",
|
||||
"standard": "^16.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -23,64 +23,66 @@ const Admin = props => {
|
||||
/>
|
||||
|
||||
{/* Création d'une fonction */}
|
||||
{isOpen ? (
|
||||
<Modal toggleModal={toggleModal}>
|
||||
<div className='Admin__Modal__container container-fluid'>
|
||||
<div className='Admin__Modal__row row'>
|
||||
<div className='col-24'>
|
||||
<div className='Admin__Modal-top-container row'>
|
||||
<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'>Crée une nouvelle fonction</h2>
|
||||
{isOpen
|
||||
? (
|
||||
<Modal toggleModal={toggleModal}>
|
||||
<div className='Admin__Modal__container container-fluid'>
|
||||
<div className='Admin__Modal__row row'>
|
||||
<div className='col-24'>
|
||||
<div className='Admin__Modal-top-container row'>
|
||||
<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'>Crée une nouvelle fonction</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='col-24'>
|
||||
<AddEditFunction
|
||||
defaultInputState={{ type: 'form' }}
|
||||
{...props}
|
||||
/>
|
||||
<div className='col-24'>
|
||||
<AddEditFunction
|
||||
defaultInputState={{ type: 'form' }}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
) : (
|
||||
<FunctionsList isAdmin token={props.user.token}>
|
||||
<div className='col-24'>
|
||||
<h1 className='Functions__title'>Administration</h1>
|
||||
<button
|
||||
onClick={toggleModal}
|
||||
style={{ margin: '0 0 40px 0' }}
|
||||
className='btn btn-dark'
|
||||
>
|
||||
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
|
||||
</Modal>
|
||||
)
|
||||
: (
|
||||
<FunctionsList isAdmin token={props.user.token}>
|
||||
<div className='col-24'>
|
||||
<h1 className='Functions__title'>Administration</h1>
|
||||
<button
|
||||
onClick={toggleModal}
|
||||
style={{ margin: '0 0 40px 0' }}
|
||||
className='btn btn-dark'
|
||||
>
|
||||
Crée une nouvelle fonction
|
||||
</button>
|
||||
</Link>
|
||||
<Link href='/admin/manageQuotes'>
|
||||
<button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>
|
||||
Gérer les citations
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</FunctionsList>
|
||||
)}
|
||||
<Link href='/admin/manageCategories'>
|
||||
<button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>
|
||||
Gérer les catégories
|
||||
</button>
|
||||
</Link>
|
||||
<Link href='/admin/manageQuotes'>
|
||||
<button style={{ margin: '0 0 0 20px' }} className='btn btn-dark'>
|
||||
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."
|
||||
/>
|
||||
|
||||
{isOpen ? (
|
||||
<Modal>
|
||||
<AddEditCategory
|
||||
handleToggleModal={toggleModal}
|
||||
defaultInputState={defaultInputState}
|
||||
{...props}
|
||||
isEditing={isEditing}
|
||||
/>
|
||||
</Modal>
|
||||
) : (
|
||||
<div className='container-fluid text-center'>
|
||||
<div className='row justify-content-center'>
|
||||
<div className='col-24'>
|
||||
<h1>Gérer les catégories</h1>
|
||||
<button
|
||||
onClick={() => {
|
||||
setDefaultInputState(defaultCategoryState)
|
||||
toggleModal()
|
||||
setIsEditing(false)
|
||||
}}
|
||||
style={{ margin: '0 0 40px 0' }}
|
||||
className='btn btn-dark'
|
||||
>
|
||||
Ajouter une catégorie
|
||||
</button>
|
||||
{isOpen
|
||||
? (
|
||||
<Modal>
|
||||
<AddEditCategory
|
||||
handleToggleModal={toggleModal}
|
||||
defaultInputState={defaultInputState}
|
||||
{...props}
|
||||
isEditing={isEditing}
|
||||
/>
|
||||
</Modal>
|
||||
)
|
||||
: (
|
||||
<div className='container-fluid text-center'>
|
||||
<div className='row justify-content-center'>
|
||||
<div className='col-24'>
|
||||
<h1>Gérer les catégories</h1>
|
||||
<button
|
||||
onClick={() => {
|
||||
setDefaultInputState(defaultCategoryState)
|
||||
toggleModal()
|
||||
setIsEditing(false)
|
||||
}}
|
||||
style={{ margin: '0 0 40px 0' }}
|
||||
className='btn btn-dark'
|
||||
>
|
||||
Ajouter une catégorie
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='row justify-content-center'>
|
||||
<div className='container-fluid'>
|
||||
<div className='col-24 table-column'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='table-row' scope='col'>
|
||||
id
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
name
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
color
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
createdAt
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
updatedAt
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Modifier
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Supprimer
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{categories.map(category => {
|
||||
return (
|
||||
<tr
|
||||
key={category.id}
|
||||
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
|
||||
})}
|
||||
<div className='row justify-content-center'>
|
||||
<div className='container-fluid'>
|
||||
<div className='col-24 table-column'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='table-row' scope='col'>
|
||||
id
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
name
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
color
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
createdAt
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
updatedAt
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Modifier
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Supprimer
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{categories.map(category => {
|
||||
return (
|
||||
<tr
|
||||
key={category.id}
|
||||
style={{ backgroundColor: category.color }}
|
||||
>
|
||||
<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>
|
||||
<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
|
||||
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>
|
||||
)}
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -266,144 +266,146 @@ const LinksList = ({
|
||||
</div>
|
||||
<div className='row justify-content-center'>
|
||||
<div className='container-fluid'>
|
||||
{!isEditing ? (
|
||||
<div className='col-24 table-column'>
|
||||
<table className='table' style={{ marginBottom: '40px' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='table-row' scope='col'>
|
||||
Liens
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Nom
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Compteur de clics
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Modifier
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Supprimer
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{linksData.rows.map((link, index) => {
|
||||
const linkJSX = (
|
||||
<>
|
||||
<td className='table-row'>
|
||||
<a href={link.url}>{link.url}</a>
|
||||
</td>
|
||||
<td className='table-row'>
|
||||
<a href={`https://s.divlo.fr/${link.shortcut}`}>
|
||||
{link.shortcut}
|
||||
</a>
|
||||
</td>
|
||||
<td className='table-row'>{link.count}</td>
|
||||
<td
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleEditLink(link)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faPen}
|
||||
style={{ width: '1.5rem' }}
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleRemoveLink(link.id)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTrash}
|
||||
style={{ width: '1.5rem' }}
|
||||
/>
|
||||
</td>
|
||||
</>
|
||||
)
|
||||
// Si c'est le dernier élément
|
||||
if (linksData.rows.length === index + 1) {
|
||||
return (
|
||||
<tr key={index} ref={lastLinkRef}>
|
||||
{linkJSX}
|
||||
</tr>
|
||||
{!isEditing
|
||||
? (
|
||||
<div className='col-24 table-column'>
|
||||
<table className='table' style={{ marginBottom: '40px' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th className='table-row' scope='col'>
|
||||
Liens
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Nom
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Compteur de clics
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Modifier
|
||||
</th>
|
||||
<th className='table-row' scope='col'>
|
||||
Supprimer
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{linksData.rows.map((link, index) => {
|
||||
const linkJSX = (
|
||||
<>
|
||||
<td className='table-row'>
|
||||
<a href={link.url}>{link.url}</a>
|
||||
</td>
|
||||
<td className='table-row'>
|
||||
<a href={`https://s.divlo.fr/${link.shortcut}`}>
|
||||
{link.shortcut}
|
||||
</a>
|
||||
</td>
|
||||
<td className='table-row'>{link.count}</td>
|
||||
<td
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleEditLink(link)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faPen}
|
||||
style={{ width: '1.5rem' }}
|
||||
/>
|
||||
</td>
|
||||
<td
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => handleRemoveLink(link.id)}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTrash}
|
||||
style={{ width: '1.5rem' }}
|
||||
/>
|
||||
</td>
|
||||
</>
|
||||
)
|
||||
}
|
||||
return <tr key={index}>{linkJSX}</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
) : (
|
||||
<Modal>
|
||||
<div className='Admin__Modal__container container-fluid'>
|
||||
<div className='Admin__Modal__row row'>
|
||||
<div className='col-24'>
|
||||
<div className='Admin__Modal-top-container row'>
|
||||
<div className='col-24'>
|
||||
<span
|
||||
onClick={toggleModal}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
left: 0
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTimes}
|
||||
style={{ width: '1.5rem', color: 'red' }}
|
||||
/>
|
||||
</span>
|
||||
// Si c'est le dernier élément
|
||||
if (linksData.rows.length === index + 1) {
|
||||
return (
|
||||
<tr key={index} ref={lastLinkRef}>
|
||||
{linkJSX}
|
||||
</tr>
|
||||
)
|
||||
}
|
||||
return <tr key={index}>{linkJSX}</tr>
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<Modal>
|
||||
<div className='Admin__Modal__container container-fluid'>
|
||||
<div className='Admin__Modal__row row'>
|
||||
<div className='col-24'>
|
||||
<div className='Admin__Modal-top-container row'>
|
||||
<div className='col-24'>
|
||||
<span
|
||||
onClick={toggleModal}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
left: 0
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTimes}
|
||||
style={{ width: '1.5rem', color: 'red' }}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className='col-24'>
|
||||
<form onSubmit={handleEditSubmit}>
|
||||
<div className='form-group'>
|
||||
<label className='form-label' htmlFor='url'>
|
||||
Entrez le lien à raccourcir :
|
||||
</label>
|
||||
<input
|
||||
value={defaultInputState.url}
|
||||
onChange={handleChange}
|
||||
type='text'
|
||||
name='url'
|
||||
id='url'
|
||||
placeholder='(e.g : https://divlo.fr)'
|
||||
className='form-control'
|
||||
/>
|
||||
</div>
|
||||
<div className='col-24'>
|
||||
<form onSubmit={handleEditSubmit}>
|
||||
<div className='form-group'>
|
||||
<label className='form-label' htmlFor='url'>
|
||||
Entrez le lien à raccourcir :
|
||||
</label>
|
||||
<input
|
||||
value={defaultInputState.url}
|
||||
onChange={handleChange}
|
||||
type='text'
|
||||
name='url'
|
||||
id='url'
|
||||
placeholder='(e.g : https://divlo.fr)'
|
||||
className='form-control'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='form-group'>
|
||||
<label className='form-label' htmlFor='shortcutName'>
|
||||
Entrez le nom du raccourci :
|
||||
</label>
|
||||
<input
|
||||
value={defaultInputState.shortcutName}
|
||||
onChange={handleChange}
|
||||
type='text'
|
||||
name='shortcutName'
|
||||
id='shortcutName'
|
||||
placeholder='(e.g : divlo)'
|
||||
className='form-control'
|
||||
/>
|
||||
</div>
|
||||
<div className='form-group'>
|
||||
<label className='form-label' htmlFor='shortcutName'>
|
||||
Entrez le nom du raccourci :
|
||||
</label>
|
||||
<input
|
||||
value={defaultInputState.shortcutName}
|
||||
onChange={handleChange}
|
||||
type='text'
|
||||
name='shortcutName'
|
||||
id='shortcutName'
|
||||
placeholder='(e.g : divlo)'
|
||||
className='form-control'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className='form-group text-center'>
|
||||
<button type='submit' className='btn btn-dark'>
|
||||
Envoyer
|
||||
</button>
|
||||
<div className='form-group text-center'>
|
||||
<button type='submit' className='btn btn-dark'>
|
||||
Envoyer
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div className='form-result text-center'>
|
||||
{isLoading ? <Loader /> : htmlParser(message)}
|
||||
</div>
|
||||
</form>
|
||||
<div className='form-result text-center'>
|
||||
{isLoading ? <Loader /> : htmlParser(message)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
)}
|
||||
</Modal>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -54,104 +54,110 @@ const PlayRightPrice = () => {
|
||||
|
||||
return (
|
||||
<div className='container-fluid'>
|
||||
{!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>
|
||||
) : (
|
||||
<>
|
||||
{!isPlaying
|
||||
? (
|
||||
<div className='row justify-content-center'>
|
||||
<div
|
||||
style={{ marginBottom: '20px' }}
|
||||
className='col-24 text-center'
|
||||
>
|
||||
<h4>{productToGuess.name}</h4>
|
||||
<img
|
||||
src={productToGuess.image}
|
||||
alt={productToGuess.name}
|
||||
className='Product__image'
|
||||
/>
|
||||
<div className='form-group text-center'>
|
||||
<button
|
||||
onClick={handlePlaying}
|
||||
type='submit'
|
||||
className='btn btn-dark'
|
||||
>
|
||||
Jouer
|
||||
</button>
|
||||
</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>
|
||||
)}
|
||||
)
|
||||
: isLoadingProduct
|
||||
? (
|
||||
<div className='row justify-content-center'>
|
||||
<Loader />
|
||||
</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 className='row justify-content-center'>
|
||||
<div
|
||||
key={index}
|
||||
className={`col-24 Price__result ${priceResultClass}`}
|
||||
style={{ marginBottom: '20px' }}
|
||||
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 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>
|
||||
)
|
||||
}
|
||||
|
@ -84,299 +84,301 @@ const Profile = props => {
|
||||
/>
|
||||
|
||||
{/* Édition du profil */}
|
||||
{isOpen ? (
|
||||
<Modal toggleModal={toggleModal}>
|
||||
<div className='Profile__container container-fluid'>
|
||||
<div className='Profile__row row'>
|
||||
<div className='col-24'>
|
||||
<div className='Profile__Modal-top-container row'>
|
||||
<div className='col-24'>
|
||||
<span
|
||||
{isOpen
|
||||
? (
|
||||
<Modal toggleModal={toggleModal}>
|
||||
<div className='Profile__container container-fluid'>
|
||||
<div className='Profile__row row'>
|
||||
<div className='col-24'>
|
||||
<div className='Profile__Modal-top-container row'>
|
||||
<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}
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
left: 0
|
||||
}}
|
||||
style={{ marginBottom: '25px' }}
|
||||
className='btn btn-dark'
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faTimes}
|
||||
style={{ width: '1.5rem', color: 'red' }}
|
||||
icon={faPen}
|
||||
style={{ cursor: 'pointer', width: '1rem' }}
|
||||
/>
|
||||
</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
|
||||
Éditez le profil
|
||||
</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>
|
||||
|
||||
{props.favoritesArray.length > 0 && (
|
||||
<div className='row justify-content-center'>
|
||||
<div className='col-24 text-center'>
|
||||
<img
|
||||
className='Profile__logo'
|
||||
src={API_URL + props.logo}
|
||||
alt={props.name}
|
||||
/>
|
||||
<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'>
|
||||
{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>
|
||||
<h2>
|
||||
Derniers <span className='important'>commentaires</span> :
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
{isAuth && user.name === props.name && (
|
||||
<button
|
||||
onClick={toggleModal}
|
||||
style={{ marginBottom: '25px' }}
|
||||
className='btn btn-dark'
|
||||
>
|
||||
<FontAwesomeIcon
|
||||
icon={faPen}
|
||||
style={{ cursor: 'pointer', width: '1rem' }}
|
||||
/>
|
||||
É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={
|
||||
<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 === 'article'
|
||||
? '/functions/[slug]'
|
||||
: `/functions/${comment.function.slug}`
|
||||
}
|
||||
as={`/functions/${comment.function.slug}`}
|
||||
>
|
||||
<a>{comment.function.title}</a>
|
||||
</Link>
|
||||
as={`/functions/${comment.function.slug}`}
|
||||
>
|
||||
<a>{comment.function.title}</a>
|
||||
</Link>
|
||||
le{' '}
|
||||
{date.format(
|
||||
new Date(comment.createdAt),
|
||||
'DD/MM/YYYY à HH:mm',
|
||||
false
|
||||
)}
|
||||
</p>
|
||||
<ReactMarkdown
|
||||
source={comment.message}
|
||||
renderers={{ code: CodeBlock }}
|
||||
/>
|
||||
{date.format(
|
||||
new Date(comment.createdAt),
|
||||
'DD/MM/YYYY à HH:mm',
|
||||
false
|
||||
)}
|
||||
</p>
|
||||
<ReactMarkdown
|
||||
source={comment.message}
|
||||
renderers={{ code: CodeBlock }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{props.quotesArray.length > 0 && (
|
||||
<div className='row justify-content-center'>
|
||||
<div className='col-24 text-center'>
|
||||
<h2>
|
||||
Dernières <span className='important'>citations</span>{' '}
|
||||
proposées (et validées) :
|
||||
</h2>
|
||||
<p>
|
||||
Citations pour la fonction{' '}
|
||||
<Link href='/functions/randomQuote'>
|
||||
<a>Générateur de citations</a>
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
{props.quotesArray.length > 0 && (
|
||||
<div className='row justify-content-center'>
|
||||
<div className='col-24 text-center'>
|
||||
<h2>
|
||||
Dernières <span className='important'>citations</span>{' '}
|
||||
proposées (et validées) :
|
||||
</h2>
|
||||
<p>
|
||||
Citations pour la fonction{' '}
|
||||
<Link href='/functions/randomQuote'>
|
||||
<a>Générateur de citations</a>
|
||||
</Link>
|
||||
.
|
||||
</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 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>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -2,11 +2,15 @@ import axios from 'axios'
|
||||
|
||||
export const API_URL = process.env.NEXT_PUBLIC_API_URL
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_URL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
const api = (() => {
|
||||
const baseURL =
|
||||
typeof window === 'undefined' ? process.env.CONTAINER_API_URL : API_URL
|
||||
return axios.create({
|
||||
baseURL,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
export default api
|
||||
|
Reference in New Issue
Block a user