├── .babelrc.js
├── .eslintrc
├── .eslintrc.json
├── .gitignore
├── .prettierrc
├── .prettierrc.json
├── Procfile
├── README.md
├── auteur
├── matcha.pgsql
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
├── server
├── config
│ ├── awsConfig.js
│ ├── database.js
│ └── mailerConfig.js
├── index.js
├── mailer
│ ├── sendForgotPasswordEmail.js
│ ├── sendLikeEmail.js
│ └── sendSigninEmail.js
├── rest
│ ├── components(C-M-R)
│ │ ├── auth
│ │ │ ├── controller.js
│ │ │ └── routes.js
│ │ ├── block
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── chatroom
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── gender
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── images
│ │ │ ├── controller.js
│ │ │ └── routes.js
│ │ ├── interests
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── like
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── match
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── notification
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── report
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── sexualOrientation
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ ├── user
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ ├── routes.js
│ │ │ ├── seed.js
│ │ │ ├── users.csv
│ │ │ └── utils.js
│ │ ├── userValidation
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ │ └── visit
│ │ │ ├── controller.js
│ │ │ ├── model.js
│ │ │ └── routes.js
│ └── middleware
│ │ └── jwt.js
└── socket
│ ├── connectedUsers.js
│ ├── disconnection.js
│ ├── joinChatroom.js
│ ├── newConnection.js
│ ├── newMessage.js
│ ├── newNotification.js
│ └── socket.js
├── src
├── assets
│ └── images
│ │ ├── heart-bg.jpg
│ │ ├── heart-confetti-background-1.png
│ │ ├── home-bg-1.jpg
│ │ ├── home-bg-2.jpg
│ │ ├── home-bg-3.jpg
│ │ ├── logo.svg
│ │ ├── mamie.jpeg
│ │ ├── pink-bg-1.jpg
│ │ ├── pink-bg-2.jpg
│ │ └── pink-bg-3.jpg
├── components
│ ├── ResetforgotPassword
│ │ ├── index.js
│ │ ├── resetforgotpassword-container.js
│ │ └── resetforgotpassword-view.js
│ ├── app
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── AuthContext.js
│ │ ├── NotLoggedRoute.js
│ │ ├── SecureRoute.js
│ │ └── index.js
│ ├── auth
│ │ ├── AuthContainer.js
│ │ └── index.js
│ ├── chat
│ │ ├── chat-container.js
│ │ ├── chat-view.js
│ │ └── index.js
│ ├── chatroom
│ │ ├── chatroom-container.js
│ │ ├── chatroom-view.js
│ │ └── index.js
│ ├── forgotpassword
│ │ ├── forgotpassword-container.js
│ │ ├── forgotpassword-view.js
│ │ └── index.js
│ ├── home
│ │ ├── home-container.js
│ │ ├── home-view.js
│ │ └── index.js
│ ├── like
│ │ ├── index.js
│ │ ├── like-container.js
│ │ └── like-view.js
│ ├── login
│ │ ├── index.js
│ │ ├── login-container.js
│ │ └── login-view.js
│ ├── nav
│ │ ├── components
│ │ │ └── notificationDrawer.js
│ │ ├── index.js
│ │ ├── nav-container.js
│ │ └── nav-view.js
│ ├── profile
│ │ ├── components
│ │ │ ├── cropper
│ │ │ │ ├── cropImage.js
│ │ │ │ └── cropper.js
│ │ │ ├── current-pictures.js
│ │ │ ├── formCheckBox.js
│ │ │ ├── inputTextShort.js
│ │ │ ├── location
│ │ │ │ ├── address-autocomplete.js
│ │ │ │ ├── cityGuess.js
│ │ │ │ └── map.js
│ │ │ ├── modal.js
│ │ │ ├── tabPanelProfileAbout.js
│ │ │ ├── tabPanelProfileParameters.js
│ │ │ └── upperBoxProfile.js
│ │ ├── index.js
│ │ ├── profile-container.js
│ │ └── profile-view.js
│ ├── profileshow
│ │ ├── components
│ │ │ ├── chipsList.js
│ │ │ └── loggedDot.js
│ │ ├── index.js
│ │ ├── profileshow-container.js
│ │ └── profileshow-view.js
│ ├── search
│ │ ├── components
│ │ │ └── search-filters.js
│ │ ├── index.js
│ │ ├── search-container.js
│ │ └── search-view.js
│ ├── shared
│ │ ├── components
│ │ │ └── media-card.js
│ │ ├── profiles-grid.js
│ │ └── title.js
│ ├── signup
│ │ ├── index.js
│ │ ├── signup-container.js
│ │ └── signup-view.js
│ ├── suggestions
│ │ ├── components
│ │ │ └── suggestions-filters.js
│ │ ├── index.js
│ │ ├── suggestions-container.js
│ │ └── suggestions-view.js
│ ├── toaster
│ │ ├── index.js
│ │ ├── toaster-container.js
│ │ └── toaster-view.js
│ ├── uservalidation
│ │ ├── index.js
│ │ └── uservalidation-view.js
│ └── visit
│ │ ├── index.js
│ │ ├── visit-container.js
│ │ └── visit-view.js
├── index.css
├── index.js
└── serviceWorker.js
└── todo.MD
/.babelrc.js:
--------------------------------------------------------------------------------
1 | const plugins = [
2 | [
3 | 'babel-plugin-transform-imports',
4 | {
5 | '@material-ui/core': {
6 | // Use "transform: '@material-ui/core/${member}'," if your bundler does not support ES modules
7 | transform: '@material-ui/core/esm/${member}',
8 | preventFullImport: true,
9 | },
10 | '@material-ui/icons': {
11 | // Use "transform: '@material-ui/icons/${member}'," if your bundler does not support ES modules
12 | transform: '@material-ui/icons/esm/${member}',
13 | preventFullImport: true,
14 | },
15 | '@lodash': {
16 | transform: '@lodash/${member}',
17 | preventFullImport: true,
18 | },
19 | },
20 | ],
21 | ];
22 |
23 | module.exports = { plugins };
24 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [],
3 | // "extends": [
4 | // "airbnb",
5 | // "prettier"
6 | // ],
7 | "plugins": ["prettier"],
8 | "rules": {
9 | "prettier/prettier": ["error"]
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["airbnb", "plugin:prettier/recommended", "prettier/react"],
3 | "env": {
4 | "browser": true,
5 | "commonjs": true,
6 | "es6": true,
7 | "jest": true,
8 | "node": true
9 | },
10 | "rules": {
11 | "no-console": "off",
12 | "class-methods-use-this": "off",
13 | "react/prop-types": 0,
14 | "jsx-a11y/href-no-hash": ["off"],
15 | "react/jsx-filename-extension": ["warn", { "extensions": [".js", ".jsx"] }],
16 | "react-hooks/exhaustive-deps": 0,
17 | "max-len": [
18 | "warn",
19 | {
20 | "code": 80,
21 | "tabWidth": 2,
22 | "comments": 80,
23 | "ignoreComments": false,
24 | "ignoreTrailingComments": true,
25 | "ignoreUrls": true,
26 | "ignoreStrings": true,
27 | "ignoreTemplateLiterals": true,
28 | "ignoreRegExpLiterals": true
29 | }
30 | ]
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 |
26 | server/config/db-log.js
27 | .env
28 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "trailingComma": "all",
4 | "tabWidth": 2,
5 | "semi": true,
6 | "singleQuote": true
7 | }
8 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "singleQuote": true,
4 | "trailingComma": "es5"
5 | }
6 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm run prodclient
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 💘 Dating app
2 | ### Built with Node, React, Postgres, Socket.io & Material-UI
3 |
4 | ## The project
5 |
6 | Matcha is a dating app with the following features:
7 | * 🌈 non-binary gender & sexual orientation choices
8 | * 💘 matching algorithm based on preferences, distance, common interests and popularity rates
9 | * 🛎 real-time notifications
10 | * 💌 chat
11 | * 🚫 possibility to block or report a user
12 |
13 | ## How to quickly try it?
14 |
15 | 👉 The project is hosted on Heroku: https://maatcha.herokuapp.com/
16 |
17 | 💡 If you don't want to go through the sign-up process, here are a few login credentials you can use:
18 | ```
19 | * maëlle16675 // xLDUD5AOSsLL2rs
20 | * romain81397 // K1XZ_qxOGYsQnr1
21 | * noémie36222 // ac3i707y193nqWs
22 | ```
23 |
24 | ## The stack
25 | ### Back
26 | * [Node](https://nodejs.org/en/)
27 | * [Express](https://expressjs.com/)
28 | * [Postgres](https://www.postgresql.org/)
29 | * [Socket.io](https://socket.io/) : real-time engine (chat + notifications)
30 | * [AWS](https://aws.amazon.com/fr/s3/) : images hosting
31 |
32 | ### Front
33 | * [React](https://reactjs.org/)
34 | * [Material-Ui](https://material-ui.com/) : React UI framework
35 |
36 | ### API we used
37 | * [Google maps](https://developers.google.com/maps/documentation/javascript/tutorial)
38 | * [Mailjet](https://www.mailjet.com/)
39 | * [Faker](https://github.com/marak/Faker.js/) : to generate fake profiles for the seed
40 |
41 | ## What it looks like
42 |
43 | [](https://freeimage.host/i/capture-decran-2020-03-30-123639.Jfl9Xn)
44 | [](https://freeimage.host/i/capture-decran-2020-03-30-132035.Jfl3g4)
45 | [](https://freeimage.host/i/capture-decran-2020-03-30-130950.JflHss)
46 | [](https://freeimage.host/i/capture-decran-2020-03-30-131939.JfldqG)
47 | [](https://freeimage.host/i/capture-decran-2020-03-30-131952.Jfl21f)
48 |
49 | ## How we've been working
50 | * 🗓 Planning the project and user stories on Trello : [The Project Board](https://trello.com/b/RLNAgAuw/matcha-launch)
51 | * 🗄 Designing the database on dbdiagram.io
52 | [](https://freeimage.host/i/capture-decran-2020-03-30-121118.JfcVO7)
53 | * 🎨 Designing simple wireframes on Figma : [The Wireframes](https://www.figma.com/file/daD5AHhiB3XmfUPdi4PhsS/Matcha?node-id=0%3A1)
54 |
55 | ## Credits
56 |
57 | 👨🏻💻👩🏻💻Built and designed by [@yann120](https://github.com/yann120) & [@Segolene-Alquier](https://github.com/Segolene-Alquier/)
58 |
--------------------------------------------------------------------------------
/auteur:
--------------------------------------------------------------------------------
1 | salquier
2 | ypetitje
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "matcha",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@babel/runtime": "^7.8.4",
7 | "@date-io/date-fns": "^1.3.13",
8 | "@material-ui/core": "^4.9.3",
9 | "@material-ui/icons": "^4.9.1",
10 | "@material-ui/lab": "^4.0.0-alpha.43",
11 | "@material-ui/pickers": "^3.2.10",
12 | "aws-sdk": "^2.624.0",
13 | "axios": "^0.19.2",
14 | "bcrypt": "^4.0.0",
15 | "bluebird": "^3.7.2",
16 | "body-parser": "^1.19.0",
17 | "bootstrap": "^4.4.1",
18 | "concurrently": "^5.1.0",
19 | "date-fns": "^2.9.0",
20 | "dotenv": "^8.2.0",
21 | "eslint-plugin-flowtype": "^4.6.0",
22 | "express": "^4.17.1",
23 | "express-jwt": "^5.3.1",
24 | "faker": "^4.1.0",
25 | "file-type": "^12.4.2",
26 | "fs": "0.0.1-security",
27 | "geolib": "^3.2.1",
28 | "google-map-react": "^1.1.6",
29 | "jquery": "^3.4.1",
30 | "jsonwebtoken": "^8.5.1",
31 | "jwt-decode": "^2.2.0",
32 | "lodash": "^4.17.15",
33 | "moment": "^2.24.0",
34 | "multiparty": "^4.2.1",
35 | "node-mailjet": "^3.3.1",
36 | "objects-to-csv": "^1.3.6",
37 | "pg-native": "^3.0.0",
38 | "pg-promise": "^9.3.6",
39 | "prop-types": "^15.7.2",
40 | "query-string": "^6.11.0",
41 | "react": "^16.12.0",
42 | "react-bootstrap": "^1.0.0-beta.16",
43 | "react-dev-utils": "^9.1.0",
44 | "react-dom": "^16.12.0",
45 | "react-easy-crop": "^1.17.1",
46 | "react-google-places-autocomplete": "^1.6.2",
47 | "react-infinite-scroll-component": "^5.0.4",
48 | "react-router-dom": "^5.1.2",
49 | "react-scripts": "^3.4.0",
50 | "react-swipeable-views": "^0.13.9",
51 | "react-toastify": "^5.5.0",
52 | "socket.io": "^2.3.0",
53 | "use-debounce": "^3.3.0",
54 | "uuid-token-generator": "^1.0.0"
55 | },
56 | "scripts": {
57 | "client-install": "npm install",
58 | "start": "npm run dev",
59 | "client": "react-scripts start",
60 | "server": "node ./server/index.js",
61 | "serverlocal": "npx nodemon ./server/index.js --ignore './src/' localhost 3001",
62 | "prodclient": "npx serve -s build",
63 | "build": "react-scripts build",
64 | "dev": "concurrently \"npm run serverlocal\" \"npm run client\"",
65 | "heroku-postbuild": "NPM_CONFIG_PRODUCTION=false npm install && npm run build",
66 | "test": "react-scripts test",
67 | "eject": "react-scripts eject"
68 | },
69 | "eslintConfig": {
70 | "extends": "react-app"
71 | },
72 | "browserslist": {
73 | "production": [
74 | ">0.2%",
75 | "not dead",
76 | "not op_mini all"
77 | ],
78 | "development": [
79 | "last 1 chrome version",
80 | "last 1 firefox version",
81 | "last 1 safari version"
82 | ]
83 | },
84 | "devDependencies": {
85 | "babel-eslint": "^10.0.3",
86 | "babel-plugin-transform-imports": "^2.0.0",
87 | "eslint": "^6.8.0",
88 | "eslint-config-airbnb": "^18.0.1",
89 | "eslint-config-prettier": "^6.10.0",
90 | "eslint-plugin-import": "^2.20.1",
91 | "eslint-plugin-jsx-a11y": "^6.2.3",
92 | "eslint-plugin-prettier": "^3.1.2",
93 | "eslint-plugin-react": "^7.18.3",
94 | "nodemon": "^1.19.4",
95 | "prettier": "^1.19.1"
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 | Matcha
14 |
18 |
19 |
20 | You need to enable JavaScript to run this app.
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 |
--------------------------------------------------------------------------------
/server/config/awsConfig.js:
--------------------------------------------------------------------------------
1 | const AWS = require('aws-sdk');
2 | const bluebird = require('bluebird');
3 |
4 | AWS.config.update({
5 | accessKeyId: process.env.AWS_ACCESS_KEY_ID,
6 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
7 | sessionToken: process.env.AWS_SESSION_TOKEN,
8 | });
9 |
10 | AWS.config.setPromisesDependency(bluebird);
11 |
12 | const s3 = new AWS.S3();
13 |
14 | module.exports.s3 = s3;
15 |
--------------------------------------------------------------------------------
/server/config/database.js:
--------------------------------------------------------------------------------
1 | const pgp = require('pg-promise')({
2 | // Initialization Options
3 | });
4 |
5 | let cn;
6 | if (process.env.ENVIRONMENT === 'production' && process.env.DATABASE_URL) {
7 | cn = process.env.DATABASE_URL;
8 | } else {
9 | cn = {
10 | host: process.env.DB_HOST,
11 | port: 5432,
12 | database: 'matcha',
13 | user: process.env.DB_USER,
14 | password: process.env.DB_PASSWORD,
15 | currentSchema: 'public',
16 | };
17 | }
18 | const db = pgp(cn);
19 |
20 | module.exports = { db, pgp };
21 |
--------------------------------------------------------------------------------
/server/config/mailerConfig.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line import/order
2 | const mailjet = require('node-mailjet').connect(
3 | process.env.MAILJET_API_KEY,
4 | process.env.MAILJET_API_SECRET,
5 | );
6 |
7 | module.exports.mailjet = mailjet;
8 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | require('dotenv').config();
4 |
5 | const port = process.env.PORT || 3001;
6 | const app = express();
7 |
8 | app.use(bodyParser.json());
9 | app.use(bodyParser.urlencoded({ extended: true }));
10 | app.use((req, res, next) => {
11 | const allowedOrigins = [
12 | 'http://localhost:3000',
13 | 'http://ypetitjean.fr:3000',
14 | 'https://maatcha.herokuapp.com',
15 | 'http://maatcha.herokuapp.com',
16 | ];
17 | const { origin } = req.headers;
18 | if (allowedOrigins.indexOf(origin) > -1) {
19 | res.header('Access-Control-Allow-Origin', origin);
20 | res.header(
21 | 'Access-Control-Allow-Methods',
22 | 'PUT, POST, GET, DELETE, OPTIONS',
23 | );
24 | res.header('Access-Control-Allow-Credentials');
25 | }
26 | res.header(
27 | 'Access-Control-Allow-Headers',
28 | 'Origin, X-Requested-With, Content-Type, Accept, x-access-token, Authorization',
29 | );
30 | next();
31 | });
32 |
33 | app.use('/users', require('./rest/components(C-M-R)/user/routes'));
34 | app.use('/genders', require('./rest/components(C-M-R)/gender/routes'));
35 | app.use('/interests', require('./rest/components(C-M-R)/interests/routes'));
36 | app.use('/auth', require('./rest/components(C-M-R)/auth/routes'));
37 | app.use('/visits', require('./rest/components(C-M-R)/visit/routes'));
38 | app.use('/matchs', require('./rest/components(C-M-R)/match/routes'));
39 | app.use('/likes', require('./rest/components(C-M-R)/like/routes'));
40 | app.use('/block', require('./rest/components(C-M-R)/block/routes'));
41 | app.use('/report', require('./rest/components(C-M-R)/report/routes'));
42 | app.use('/chat', require('./rest/components(C-M-R)/chatroom/routes'));
43 | app.use(
44 | '/notification',
45 | require('./rest/components(C-M-R)/notification/routes'),
46 | );
47 | app.use(
48 | '/validation',
49 | require('./rest/components(C-M-R)/userValidation/routes'),
50 | );
51 | app.use('/images', require('./rest/components(C-M-R)/images/routes'));
52 |
53 | const server = require('http').Server(app);
54 | global.io = require('socket.io')(server);
55 | require('./socket/socket')();
56 |
57 | server.listen(port, () => {
58 | console.log(`Matcha is listening on port ${port}!`);
59 | });
60 |
--------------------------------------------------------------------------------
/server/mailer/sendForgotPasswordEmail.js:
--------------------------------------------------------------------------------
1 | const { mailjet } = require('../config/mailerConfig');
2 |
3 | const sendForgotPasswordEmail = async (email, firstname, token) => {
4 | const request = mailjet.post('send', { version: 'v3.1' }).request({
5 | Messages: [
6 | {
7 | From: {
8 | Email: 'yann.petitjean06@gmail.com',
9 | Name: 'Matcha',
10 | },
11 | To: [
12 | {
13 | Email: email,
14 | Name: firstname,
15 | },
16 | ],
17 | TemplateID: 1091114,
18 | TemplateLanguage: true,
19 | Subject: 'Matcha - Mot de passe oublié',
20 | Variables: {
21 | firstname,
22 | COMFIRMATION_TOKEN: token,
23 | },
24 | },
25 | ],
26 | });
27 |
28 | await request.catch(err => {
29 | if (process.env.VERBOSE === 'true') console.log(err);
30 | });
31 | };
32 |
33 | module.exports.sendForgotPasswordEmail = sendForgotPasswordEmail;
34 |
--------------------------------------------------------------------------------
/server/mailer/sendLikeEmail.js:
--------------------------------------------------------------------------------
1 | const { mailjet } = require('../config/mailerConfig');
2 | const User = require('./../rest/components(C-M-R)/user/model');
3 |
4 | const user = new User();
5 |
6 | const sendLikeEmail = async (likedUserId, likingUserId) => {
7 | const [{ firstname, email }] = await user.getByFiltered('id', likedUserId, [
8 | 'firstname',
9 | 'email',
10 | ]);
11 | const [{ username: likingUser }] = await user.getByFiltered(
12 | 'id',
13 | likingUserId,
14 | ['username'],
15 | );
16 | const request = mailjet.post('send', { version: 'v3.1' }).request({
17 | Messages: [
18 | {
19 | From: {
20 | Email: 'yann.petitjean06@gmail.com',
21 | Name: 'Matcha',
22 | },
23 | To: [
24 | {
25 | Email: email,
26 | Name: firstname,
27 | },
28 | ],
29 | TemplateID: 1197417,
30 | TemplateLanguage: true,
31 | Subject: 'Someone likes you on Matcha 🔥',
32 | Variables: {
33 | firstname,
34 | likinguser: likingUser,
35 | },
36 | },
37 | ],
38 | });
39 |
40 | await request.catch(err => {
41 | if (process.env.VERBOSE === 'true') console.log(err);
42 | });
43 | };
44 |
45 | module.exports.sendLikeEmail = sendLikeEmail;
46 |
--------------------------------------------------------------------------------
/server/mailer/sendSigninEmail.js:
--------------------------------------------------------------------------------
1 | const { mailjet } = require('../config/mailerConfig');
2 |
3 | const sendSigninEmail = async (email, firstname, token) => {
4 | const request = mailjet.post('send', { version: 'v3.1' }).request({
5 | Messages: [
6 | {
7 | From: {
8 | Email: 'yann.petitjean06@gmail.com',
9 | Name: 'Matcha',
10 | },
11 | To: [
12 | {
13 | Email: email,
14 | Name: firstname,
15 | },
16 | ],
17 | TemplateID: 1085893,
18 | TemplateLanguage: true,
19 | Subject: 'Bienvenue chez Matcha',
20 | Variables: {
21 | firstname,
22 | COMFIRMATION_TOKEN: token,
23 | },
24 | },
25 | ],
26 | });
27 |
28 | await request.catch(err => {
29 | if (process.env.VERBOSE === 'true') console.log(err);
30 | });
31 | };
32 |
33 | module.exports.sendSigninEmail = sendSigninEmail;
34 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/auth/controller.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | const bcrypt = require('bcrypt');
3 | const User = require('../user/model');
4 |
5 | const user = new User();
6 |
7 | const secret = 'mignon4ever';
8 |
9 | async function booleanToken(request, response) {
10 | let token =
11 | request.headers['x-access-token'] || request.headers.authorization;
12 | if (token) {
13 | if (token.startsWith('Bearer ')) {
14 | token = token.slice(7, token.length);
15 | }
16 | jwt.verify(token, secret, (err, decoded) => {
17 | if (err) {
18 | return response.json({
19 | success: false,
20 | message: 'Token is not valid',
21 | });
22 | }
23 | request.decoded = decoded;
24 | return request.decoded;
25 | });
26 | const userId = request.decoded.userid;
27 | const { lon, lat } = request.query;
28 | if (lon && lat) {
29 | const location = [parseFloat(lon), parseFloat(lat)];
30 | user.updateById(userId, { location });
31 | }
32 | const userData = await user.getByFiltered('id', userId, [
33 | 'id',
34 | 'firstname',
35 | 'surname',
36 | 'username',
37 | 'email',
38 | 'location',
39 | 'birthDate',
40 | 'popularityRate',
41 | 'gender',
42 | 'sexualOrientation',
43 | 'description',
44 | 'interests',
45 | 'images',
46 | 'profilePicture',
47 | 'notificationMail',
48 | 'notificationPush',
49 | ]);
50 |
51 | return response.json({
52 | success: true,
53 | message: 'Token is valid',
54 | data: userData[0],
55 | });
56 | }
57 | return response.json({
58 | success: false,
59 | message: 'Auth token not supplied',
60 | });
61 | }
62 |
63 | async function login(request, response) {
64 | const { username, password, lon, lat } = request.body;
65 |
66 | if (process.env.VERBOSE === 'true')
67 | console.log('User submitted: ', username, password);
68 | try {
69 | const query = await user.getBy('username', username);
70 | if (query.length <= 0) {
71 | if (process.env.VERBOSE === 'true')
72 | console.log(username, " doesn't exist");
73 | response.status(200).json({
74 | success: false,
75 | token: null,
76 | err: `${username} doesn't exist`,
77 | });
78 | return;
79 | }
80 | const [visitor] = query;
81 | if (visitor.suspended) {
82 | response.status(200).json({
83 | success: false,
84 | token: null,
85 | err: 'Your account has been suspended',
86 | });
87 | return;
88 | }
89 | if (process.env.VERBOSE === 'true')
90 | console.log('compare : ', visitor.password, password);
91 | if (bcrypt.compareSync(password, visitor.password)) {
92 | if (visitor.validated) {
93 | const token = jwt.sign({ userid: visitor.id }, 'mignon4ever', {
94 | expiresIn: '1d',
95 | });
96 | if (lon && lat) {
97 | const location = [parseFloat(lon), parseFloat(lat)];
98 | user.updateById(visitor.id, { location, lastConnection: 'now()' });
99 | } else {
100 | await user.updateById(visitor.id, {
101 | lastConnection: 'now()',
102 | });
103 | }
104 | response.json({
105 | success: true,
106 | token,
107 | err: null,
108 | });
109 | } else {
110 | response.status(200).json({
111 | success: false,
112 | token: null,
113 | err: 'The user is not validated',
114 | });
115 | }
116 | } else {
117 | if (process.env.VERBOSE === 'true')
118 | console.log('The login and password do not match!');
119 | response.status(200).json({
120 | success: false,
121 | token: null,
122 | err: 'The login and password do not match!',
123 | });
124 | }
125 | } catch (err) {
126 | if (process.env.VERBOSE === 'true') console.log(err);
127 | response.status(206).send(err);
128 | }
129 | }
130 |
131 | async function logout(request, response) {
132 | try {
133 | // let call = await gender.getAll()
134 | // response.status(200).json(call)
135 | } catch (err) {
136 | if (process.env.VERBOSE === 'true') console.log(err);
137 | response.status(206).send(err);
138 | }
139 | }
140 |
141 | module.exports.login = login;
142 | module.exports.logout = logout;
143 | module.exports.booleanToken = booleanToken;
144 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/auth/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const router = express.Router();
4 | const { login, logout, booleanToken } = require('./controller');
5 |
6 | // login user
7 | router.post('/login', login);
8 |
9 | // logout user
10 | router.post('/logout', logout);
11 |
12 | // check token
13 | router.get('/checkToken', booleanToken);
14 |
15 | module.exports = router;
16 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/block/controller.js:
--------------------------------------------------------------------------------
1 | const Block = require('./model');
2 |
3 | const blocks = new Block();
4 |
5 | async function blockUnblockUserId(request, response) {
6 | const blockingUser = request.decoded.userid;
7 | const blockedUser = parseInt(request.params.id, 10);
8 | if (blockedUser === blockingUser) {
9 | return response
10 | .status(200)
11 | .json({ success: false, error: 'You can not block yourself!' });
12 | }
13 | try {
14 | const alreadyBlocked = await blocks.exists(blockingUser, blockedUser);
15 | let query;
16 | if (alreadyBlocked) {
17 | query = await blocks.delete(blockingUser, blockedUser);
18 | } else {
19 | query = await blocks.create(blockingUser, blockedUser);
20 | }
21 | return response.status(200).json(query);
22 | } catch (err) {
23 | if (process.env.VERBOSE === 'true') console.log(err);
24 | return response.status(206).send(err);
25 | }
26 | }
27 |
28 | module.exports.blockUnblockUserId = blockUnblockUserId;
29 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/block/model.js:
--------------------------------------------------------------------------------
1 | const { db } = require('../../../config/database');
2 |
3 | class Block {
4 | isValidType(type) {
5 | const authorizedTypes = ['id', 'blockingUser', 'blockedUser'];
6 | return authorizedTypes.some(authorizedType => {
7 | return type === authorizedType;
8 | });
9 | }
10 |
11 | async create(blockingUserId, blockedUserId) {
12 | try {
13 | if (process.env.VERBOSE === 'true')
14 | console.log(
15 | `INSERT INTO public."Block" (blockingUser, blockedUser) VALUES (${blockingUserId}, ${blockedUserId} RETURNING id)`,
16 | );
17 | return await db
18 | .any(
19 | 'INSERT INTO public."Block" ("blockingUser", "blockedUser") VALUES ($1, $2) RETURNING id',
20 | [blockingUserId, blockedUserId],
21 | )
22 | .then(data => {
23 | return {
24 | success: true,
25 | created: true,
26 | id: data[0].id,
27 | };
28 | });
29 | } catch (err) {
30 | if (process.env.VERBOSE === 'true')
31 | console.log(err, 'in model Block.create()');
32 | return { created: false, error: err };
33 | }
34 | }
35 |
36 | async exists(blockingUser, blockedUser) {
37 | try {
38 | if (process.env.VERBOSE === 'true')
39 | console.log(
40 | `SELECT exists(SELECT from public."Block" WHERE "blockingUser" = ${blockingUser} AND "blockedUser" = ${blockedUser})`,
41 | );
42 | const result = await db.any(
43 | `SELECT exists(SELECT from public."Block" WHERE "blockingUser" = $1 AND "blockedUser" = $2);`,
44 | [blockingUser, blockedUser],
45 | );
46 | return result[0].exists;
47 | } catch (err) {
48 | if (process.env.VERBOSE === 'true')
49 | console.log(err, 'in model Block.exists()');
50 | return null;
51 | }
52 | }
53 |
54 | async delete(blockingUser, blockedUser) {
55 | try {
56 | if (process.env.VERBOSE === 'true')
57 | console.log(
58 | `DELETE FROM public."Block" WHERE "blockingUser" = ${blockingUser} AND "blockedUser" = ${blockedUser}`,
59 | );
60 | await db.any(
61 | 'DELETE FROM public."Block" WHERE "blockingUser" = $1 AND "blockedUser" = $2 ',
62 | [blockingUser, blockedUser],
63 | );
64 | return { success: true, deleted: true };
65 | } catch (err) {
66 | if (process.env.VERBOSE === 'true')
67 | console.log(err, 'in model User.delete()');
68 | return { deleted: false, error: err };
69 | }
70 | }
71 | }
72 |
73 | module.exports = Block;
74 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/block/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const {
6 | // getLikesFromCurrentUser,
7 | blockUnblockUserId,
8 | } = require('./controller');
9 |
10 | // get the list of blocked users from the current user
11 | // router.get('/', checkToken, getLikesFromCurrentUser);
12 | router.post('/block-unblock/:id', checkToken, blockUnblockUserId);
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/chatroom/controller.js:
--------------------------------------------------------------------------------
1 | const Chat = require('./model');
2 | const Match = require('../match/model');
3 | const User = require('../user/model');
4 |
5 | const messages = new Chat();
6 | const match = new Match();
7 | const user = new User();
8 |
9 | async function getMatchsFromCurrentUser(request, response) {
10 | try {
11 | const id = request.decoded.userid;
12 | const call = await messages.getBy(['user1', 'user2'], id);
13 | response.status(200).json(call);
14 | } catch (err) {
15 | if (process.env.VERBOSE === 'true') console.log(err);
16 | response.status(206).send(err);
17 | }
18 | }
19 |
20 | async function getMessagesFromMatchId(request, response) {
21 | try {
22 | const { id: matchId } = request.params;
23 | const userId = request.decoded.userid;
24 | let otherUserId;
25 | if (await messages.userCanAccessMatch(matchId, userId)) {
26 | let call = await messages.getAll(matchId);
27 | const usersIds = await match.getUsersFromMatchId(matchId);
28 | if (usersIds[0] === userId) {
29 | [, otherUserId] = usersIds;
30 | }
31 | if (usersIds[1] === userId) {
32 | [otherUserId] = usersIds;
33 | }
34 | const otherUserInfo = await user.getByFiltered('id', otherUserId, [
35 | 'profilePicture',
36 | 'firstname',
37 | 'username',
38 | ]);
39 | call = {
40 | messages: call,
41 | profilePicture: otherUserInfo[0].profilePicture,
42 | firstname: otherUserInfo[0].firstname,
43 | username: otherUserInfo[0].username,
44 | };
45 | if (process.env.VERBOSE === 'true') console.log('call', call);
46 | messages.updateRead(matchId, userId);
47 | response.status(200).json(call);
48 | } else {
49 | response.status(200).json({
50 | success: false,
51 | message: "You don't have access to that chatroom, nice try!",
52 | });
53 | }
54 | } catch (err) {
55 | if (process.env.VERBOSE === 'true') console.log(err);
56 | response.status(206).send(err);
57 | }
58 | }
59 |
60 | async function numberOfUnreadMessages(request, response) {
61 | const id = request.decoded.userid;
62 | try {
63 | const call = await messages.numberUnread(id);
64 | response.status(200).json(call);
65 | } catch (err) {
66 | if (process.env.VERBOSE === 'true') console.log(err);
67 | response.status(206).send(err);
68 | }
69 | }
70 |
71 | module.exports.getMatchsFromCurrentUser = getMatchsFromCurrentUser;
72 | module.exports.getMessagesFromMatchId = getMessagesFromMatchId;
73 | module.exports.numberOfUnreadMessages = numberOfUnreadMessages;
74 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/chatroom/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const {
6 | getMatchsFromCurrentUser,
7 | getMessagesFromMatchId,
8 | numberOfUnreadMessages,
9 | } = require('./controller');
10 |
11 | // list of all Matchs from current user - Chat
12 | router.get('/', checkToken, getMatchsFromCurrentUser);
13 | // get number of unread messages by user id - Chat
14 | router.get('/total', checkToken, numberOfUnreadMessages);
15 | // get all messages by match id - Chat
16 | router.get('/:id', checkToken, getMessagesFromMatchId);
17 |
18 | module.exports = router;
19 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/gender/controller.js:
--------------------------------------------------------------------------------
1 | const Gender = require('./model');
2 |
3 | const gender = new Gender();
4 |
5 | async function getGenders(request, response) {
6 | try {
7 | const call = await gender.getAll();
8 | response.status(200).json(call);
9 | } catch (err) {
10 | if (process.env.VERBOSE === 'true') console.log(err);
11 | response.status(206).send(err);
12 | }
13 | }
14 |
15 | async function getGenderById(request, response) {
16 | const id = parseInt(request.params.id, 10);
17 | try {
18 | const call = await gender.getBy('id', id);
19 | response.status(200).json(call);
20 | } catch (err) {
21 | if (process.env.VERBOSE === 'true') console.log(err);
22 | response.status(206).send(err);
23 | }
24 | }
25 |
26 | module.exports.getGenders = getGenders;
27 | module.exports.getGenderById = getGenderById;
28 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/gender/model.js:
--------------------------------------------------------------------------------
1 | const { db } = require('../../../config/database');
2 |
3 | class Gender {
4 | isValidType(type) {
5 | const authorizedTypes = ['id', 'name'];
6 | return authorizedTypes.some(authorizedType => {
7 | return type === authorizedType;
8 | });
9 | }
10 |
11 | async getBy(type, value) {
12 | try {
13 | if (!this.isValidType(type)) {
14 | if (process.env.VERBOSE === 'true')
15 | console.log(`Gender.getBy(): ${type} is not an authorized type`);
16 | return null;
17 | }
18 | if (process.env.VERBOSE === 'true')
19 | console.log(`SELECT * FROM public."Gender" WHERE ${type} = ${value}`);
20 | const result = await db.any(
21 | `SELECT * FROM public."Gender" WHERE $1:name = $2`,
22 | [type, value],
23 | );
24 | return result;
25 | } catch (err) {
26 | if (process.env.VERBOSE === 'true')
27 | console.log(err, 'in model Gender.getBy()');
28 | return null;
29 | }
30 | }
31 |
32 | async getAll() {
33 | try {
34 | if (process.env.VERBOSE === 'true')
35 | console.log('SELECT * FROM public."Gender"');
36 | const result = await db.any('SELECT * FROM public."Gender"');
37 | return result;
38 | } catch (err) {
39 | if (process.env.VERBOSE === 'true')
40 | console.log(err, 'in model Gender.getAll()');
41 | return null;
42 | }
43 | }
44 |
45 | async exists(type, value) {
46 | try {
47 | if (!value) return false;
48 | if (!this.isValidType(type)) {
49 | if (process.env.VERBOSE === 'true')
50 | console.log(`Gender.exists(): ${type} is not an authorized type`);
51 | return null;
52 | }
53 | if (process.env.VERBOSE === 'true')
54 | console.log(
55 | `SELECT exists(SELECT from public."Gender" WHERE ${type} = ${value})`,
56 | );
57 | const result = await db.any(
58 | `SELECT exists(SELECT from public."Gender" WHERE $1:name = $2);`,
59 | [type, value],
60 | );
61 | return result[0].exists;
62 | } catch (err) {
63 | if (process.env.VERBOSE === 'true')
64 | console.log(err, 'in model Gender.exists()');
65 | return null;
66 | }
67 | }
68 | }
69 |
70 | module.exports = Gender;
71 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/gender/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const router = express.Router();
4 | const { getGenders, getGenderById } = require('./controller');
5 |
6 | // list of all Genders - Gender
7 | router.get('/', getGenders);
8 | // get Gender by id - Gender
9 | router.get('/:id', getGenderById);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/images/controller.js:
--------------------------------------------------------------------------------
1 | const multiparty = require('multiparty');
2 | const fs = require('fs');
3 | const fileType = require('file-type');
4 | const { s3 } = require('./../../../config/awsConfig');
5 | const User = require('../user/model');
6 |
7 | const user = new User();
8 |
9 | const uploadFile = (buffer, name, type) => {
10 | const params = {
11 | ACL: 'public-read',
12 | Body: buffer,
13 | Bucket: process.env.S3_BUCKET,
14 | ContentType: type.mime,
15 | Key: `${name}.${type.ext}`,
16 | };
17 | return s3.upload(params).promise();
18 | };
19 |
20 | const deleteFile = url => {
21 | const name = url.match(/[^\/]+\/[^\/]+\/[^\/]+$/)[0];
22 | const params = {
23 | Bucket: process.env.S3_BUCKET,
24 | Key: name,
25 | };
26 | return s3.deleteObject(params).promise();
27 | };
28 |
29 | async function uploadImage(request, response) {
30 | const id = request.decoded.userid;
31 | try {
32 | const length = await user.getByFiltered('id', id, ['images']).then(data => {
33 | return data[0].images.length;
34 | });
35 | if (length >= 5) {
36 | return response.status(400).send('A user can only upload 5 pictures');
37 | }
38 | } catch (error) {
39 | if (process.env.VERBOSE === 'true') console.log(error);
40 | return response.status(400).send(error);
41 | }
42 | const form = new multiparty.Form();
43 | form.parse(request, async (error, fields, files) => {
44 | if (error) throw new Error(error);
45 | try {
46 | const { path } = files.file[0];
47 | const buffer = fs.readFileSync(path);
48 | const type = fileType(buffer);
49 | const timestamp = Date.now().toString();
50 | const fileName = `${process.env.ENVIRONMENT}/${id}/${timestamp}`;
51 | const data = await uploadFile(buffer, fileName, type);
52 | await user.addElementToArrayById(id, 'images', data.Location);
53 | await user.updateProfilePictureIfNotExist(id);
54 | return response.status(200).send(data);
55 | } catch (error) {
56 | if (process.env.VERBOSE === 'true') console.log(error);
57 | return response.status(400).send(error);
58 | }
59 | });
60 | }
61 |
62 | async function deleteImage(request, response) {
63 | const id = request.decoded.userid;
64 | const { url } = request.body;
65 | try {
66 | await user.deleteElementToArrayById(id, 'images', url);
67 | await user.updateProfilePictureIfNotExist(id);
68 | if (!url.includes('generated.photos')) {
69 | await deleteFile(url);
70 | }
71 | return response.status(200).send({ success: true });
72 | } catch (error) {
73 | if (process.env.VERBOSE === 'true') console.log(error);
74 | return response.status(400).send({ success: false, error });
75 | }
76 | }
77 |
78 | module.exports.uploadImage = uploadImage;
79 | module.exports.deleteImage = deleteImage;
80 | module.exports.deleteFile = deleteFile;
81 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/images/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const { uploadImage, deleteImage } = require('./controller');
6 |
7 | // Upload an image
8 | router.post('/upload', checkToken, uploadImage);
9 | router.post('/delete', checkToken, deleteImage);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/interests/controller.js:
--------------------------------------------------------------------------------
1 | const Interest = require('./model');
2 |
3 | const interests = new Interest();
4 |
5 | async function getInterests(request, response) {
6 | try {
7 | const call = await interests.getAll();
8 | response.status(200).json(call);
9 | } catch (err) {
10 | if (process.env.VERBOSE === 'true') console.log(err);
11 | response.status(206).send(err);
12 | }
13 | }
14 |
15 | async function getInterestById(request, response) {
16 | const id = parseInt(request.params.id, 10);
17 | try {
18 | const call = await interests.getBy('id', id);
19 | response.status(200).json(call);
20 | } catch (err) {
21 | if (process.env.VERBOSE === 'true') console.log(err);
22 | response.status(206).send(err);
23 | }
24 | }
25 |
26 | module.exports.getInterests = getInterests;
27 | module.exports.getInterestById = getInterestById;
28 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/interests/model.js:
--------------------------------------------------------------------------------
1 | const { db } = require('../../../config/database');
2 |
3 | class Interest {
4 | isValidType(type) {
5 | const authorizedTypes = ['id', 'name'];
6 | return authorizedTypes.some(authorizedType => {
7 | return type === authorizedType;
8 | });
9 | }
10 |
11 | async getBy(type, value) {
12 | try {
13 | if (!this.isValidType(type)) {
14 | if (process.env.VERBOSE === 'true')
15 | console.log(`Interest.getBy(): ${type} is not an authorized type`);
16 | return null;
17 | }
18 | if (process.env.VERBOSE === 'true')
19 | console.log(`SELECT * FROM public."Interest" WHERE ${type} = ${value}`);
20 | const result = await db.any(
21 | `SELECT * FROM public."Interest" WHERE $1:name = $2`,
22 | [type, value],
23 | );
24 | return result;
25 | } catch (err) {
26 | if (process.env.VERBOSE === 'true')
27 | console.log(err, 'in model Interest.getBy()');
28 | return null;
29 | }
30 | }
31 |
32 | async getAll() {
33 | try {
34 | if (process.env.VERBOSE === 'true')
35 | console.log('SELECT * FROM public."Interest"');
36 | const result = await db.any('SELECT * FROM public."Interest"');
37 | return result;
38 | } catch (err) {
39 | if (process.env.VERBOSE === 'true')
40 | console.log(err, 'in model Interest.getAll()');
41 | return null;
42 | }
43 | }
44 |
45 | async exists(type, value) {
46 | try {
47 | if (!value) return false;
48 | if (!this.isValidType(type)) {
49 | if (process.env.VERBOSE === 'true')
50 | console.log(`Interest.exists(): ${type} is not an authorized type`);
51 | return null;
52 | }
53 | if (process.env.VERBOSE === 'true')
54 | console.log(
55 | `SELECT exists(SELECT from public."Interest" WHERE ${type} = ${value})`,
56 | );
57 | const result = await db.none(
58 | `SELECT exists(SELECT from public."Interest" WHERE id = ALL($2));`,
59 | [value],
60 | );
61 | return result[0].exists;
62 | } catch (err) {
63 | if (process.env.VERBOSE === 'true')
64 | console.log(err, 'in model Interest.exists()');
65 | return null;
66 | }
67 | }
68 | }
69 |
70 | module.exports = Interest;
71 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/interests/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const router = express.Router();
4 | const { getInterests, getInterestById } = require('./controller');
5 |
6 | // list of all Interests - Interest
7 | router.get('/', getInterests);
8 | // get Interest by id - Interest
9 | router.get('/:id', getInterestById);
10 |
11 | module.exports = router;
12 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/like/controller.js:
--------------------------------------------------------------------------------
1 | const Like = require('./model');
2 | const Match = require('./../match/model');
3 | const Block = require('./../block/model');
4 | const User = require('./../user/model');
5 | const Chat = require('./../chatroom/model');
6 | const _ = require('lodash');
7 |
8 | const { sendLikeEmail } = require('../../../mailer/sendLikeEmail');
9 | const { isConnected } = require('./../../../socket/newConnection');
10 | const newNotification = require('../../../socket/newNotification');
11 |
12 | const likes = new Like();
13 | const block = new Block();
14 | const matchs = new Match();
15 | const user = new User();
16 | const chat = new Chat();
17 |
18 | async function getLikesFromCurrentUser(request, response) {
19 | const id = request.decoded.userid;
20 | try {
21 | let call = await likes.getBy('likedUser', id);
22 | call = _.map(call, like => {
23 | return { ...like, connected: isConnected(like.likingUser) };
24 | });
25 | response.status(200).json(call);
26 | } catch (err) {
27 | if (process.env.VERBOSE === 'true') console.log(err);
28 | response.status(206).send(err);
29 | }
30 | }
31 |
32 | async function likeUnlikeUserId(request, response) {
33 | const likingUser = request.decoded.userid;
34 | const likedUser = parseInt(request.params.id, 10);
35 | if (likedUser === likingUser) {
36 | return response
37 | .status(200)
38 | .json({ success: false, error: 'You can not like yourself!' });
39 | }
40 | if (await block.exists(likedUser, likingUser)) {
41 | return response.status(200).json({
42 | success: false,
43 | blocked: true,
44 | message: 'You have been blocked by this user!',
45 | });
46 | }
47 | try {
48 | const alreadyLiked = await likes.exists(likingUser, likedUser);
49 | let query;
50 | if (alreadyLiked) {
51 | query = await likes.delete(likingUser, likedUser);
52 | if (query.unmatch) {
53 | const matchId = await matchs.getMatchId(likingUser, likedUser);
54 | newNotification(likedUser, likingUser, 'unmatch');
55 | await chat.delete(matchId);
56 | matchs.delete(likingUser, likedUser);
57 | }
58 | } else {
59 | query = await likes.create(likingUser, likedUser);
60 | if (query.match) {
61 | const matchQuery = await matchs.create(likingUser, likedUser);
62 | newNotification(likedUser, likingUser, 'match');
63 | query.matchId = matchQuery.id;
64 | } else {
65 | newNotification(likedUser, likingUser, 'like');
66 | sendLikeEmail(likedUser, likingUser);
67 | }
68 | }
69 | user.updatePopularityRate(likedUser);
70 | response.status(200).json(query);
71 | } catch (err) {
72 | if (process.env.VERBOSE === 'true') console.log(err);
73 | response.status(206).send(err);
74 | }
75 | }
76 |
77 | module.exports.getLikesFromCurrentUser = getLikesFromCurrentUser;
78 | module.exports.likeUnlikeUserId = likeUnlikeUserId;
79 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/like/model.js:
--------------------------------------------------------------------------------
1 | const { db } = require('../../../config/database');
2 |
3 | class Like {
4 | isValidType(type) {
5 | const authorizedTypes = ['id', 'likingUser', 'likedUser'];
6 | return authorizedTypes.some(authorizedType => {
7 | return type === authorizedType;
8 | });
9 | }
10 |
11 | async create(likingUserId, likedUserId) {
12 | try {
13 | if (process.env.VERBOSE === 'true')
14 | console.log(
15 | `INSERT INTO public."Like" (likingUser, likedUser, date) VALUES (${likingUserId}, ${likedUserId}, Now() RETURNING id)`,
16 | );
17 | return await db
18 | .any(
19 | 'INSERT INTO public."Like" ("likingUser", "likedUser", date) VALUES ($1, $2, NOW()) RETURNING id, EXISTS(SELECT * FROM public."Like" WHERE "likingUser" = $2 AND "likedUser" = $1) AS match',
20 | [likingUserId, likedUserId],
21 | )
22 | .then(data => {
23 | return {
24 | success: true,
25 | created: true,
26 | id: data[0].id,
27 | match: data[0].match,
28 | };
29 | });
30 | } catch (err) {
31 | if (process.env.VERBOSE === 'true')
32 | console.log(err, 'in model Like.create()');
33 | return { created: false, error: err };
34 | }
35 | }
36 |
37 | async getBy(type, value) {
38 | try {
39 | if (!this.isValidType(type)) {
40 | if (process.env.VERBOSE === 'true')
41 | console.log(`Like.getBy(): ${type} is not an authorized type`);
42 | return null;
43 | }
44 | if (process.env.VERBOSE === 'true')
45 | console.log(
46 | `SELECT firstname, username, birthDate, location, popularityRate, profilePicture, date
47 | FROM public."Like"
48 | WHERE ${type} = ${value}`,
49 | );
50 | const result = await db.any(
51 | `SELECT firstname, username, "birthDate", location, "popularityRate", "profilePicture", "likingUser" AS visitor, "likedUser" AS visited, date,
52 | EXISTS(SELECT * FROM public."Like" AS secondlike WHERE secondlike."likingUser" = $2 AND secondlike."likedUser" = "Like"."likingUser") AS liking
53 | FROM public."Like" , public."User"
54 | WHERE $1:name = $2 AND "Like"."likingUser" = "User".id
55 | AND NOT EXISTS (
56 | SELECT *
57 | FROM public."Block"
58 | WHERE "blockedUser" = $2
59 | AND "blockingUser" = "likingUser"
60 | )
61 | ORDER BY date DESC`,
62 | [type, value],
63 | );
64 | result.forEach(element => {
65 | element.match = element.liking;
66 | });
67 | return result;
68 | } catch (err) {
69 | if (process.env.VERBOSE === 'true')
70 | console.log(err, 'in model Like.getBy()');
71 | return null;
72 | }
73 | }
74 |
75 | async getAll() {
76 | try {
77 | if (process.env.VERBOSE === 'true')
78 | console.log('SELECT * FROM public."Like"');
79 | const result = await db.any('SELECT * FROM public."Like"');
80 | return result;
81 | } catch (err) {
82 | if (process.env.VERBOSE === 'true')
83 | console.log(err, 'in model Like.getAll()');
84 | return null;
85 | }
86 | }
87 |
88 | async exists(likingUser, likedUser) {
89 | try {
90 | if (process.env.VERBOSE === 'true')
91 | console.log(
92 | `SELECT exists(SELECT from public."Like" WHERE "likingUser" = ${likingUser} AND "likedUser" = ${likedUser})`,
93 | );
94 | const result = await db.any(
95 | `SELECT exists(SELECT from public."Like" WHERE "likingUser" = $1 AND "likedUser" = $2);`,
96 | [likingUser, likedUser],
97 | );
98 | return result[0].exists;
99 | } catch (err) {
100 | if (process.env.VERBOSE === 'true')
101 | console.log(err, 'in model Like.exists()');
102 | return null;
103 | }
104 | }
105 |
106 | async relationship(visitorUser, visitedUser) {
107 | try {
108 | if (process.env.VERBOSE === 'true')
109 | console.log(
110 | `SELECT exists(SELECT from public."Like" WHERE "likingUser" = $1 AND "likedUser" = $2) AS visitorlikevisited, exists(SELECT from public."Like" WHERE "likedUser" = $1 AND "likingUser" = $2) AS visitedlikevisitor`,
111 | );
112 | const result = await db.any(
113 | `SELECT exists(SELECT from public."Like" WHERE "likingUser" = $1 AND "likedUser" = $2) AS visitorlikevisited, exists(SELECT from public."Like" WHERE "likedUser" = $1 AND "likingUser" = $2) AS visitedlikevisitor`,
114 | [visitorUser, visitedUser],
115 | );
116 | result[0].match =
117 | result[0].visitorlikevisited && result[0].visitedlikevisitor;
118 | return result[0];
119 | } catch (err) {
120 | if (process.env.VERBOSE === 'true')
121 | console.log(err, 'in model Like.exists()');
122 | return null;
123 | }
124 | }
125 |
126 | async delete(likingUser, likedUser) {
127 | try {
128 | if (process.env.VERBOSE === 'true')
129 | console.log(
130 | `DELETE FROM public."Like" WHERE "likingUser" = ${likingUser} AND "likedUser" = ${likedUser}`,
131 | );
132 | const result = await db.any(
133 | 'DELETE FROM public."Like" WHERE "likingUser" = $1 AND "likedUser" = $2 RETURNING EXISTS(SELECT from public."Like" WHERE "likedUser" = $1 AND "likingUser" = $2) AS unmatch',
134 | [likingUser, likedUser],
135 | );
136 | if (process.env.VERBOSE === 'true') console.log(result[0].unmatch);
137 | return { success: true, deleted: true, unmatch: result[0].unmatch };
138 | } catch (err) {
139 | if (process.env.VERBOSE === 'true')
140 | console.log(err, 'in model User.delete()');
141 | return { deleted: false, error: err };
142 | }
143 | }
144 | }
145 |
146 | module.exports = Like;
147 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/like/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const {
6 | // getLikesByLikingUserId,
7 | // getLikesByLikedUserId,
8 | getLikesFromCurrentUser,
9 | likeUnlikeUserId,
10 | } = require('./controller');
11 |
12 | // get the list of Like from the current user
13 | router.get('/', checkToken, getLikesFromCurrentUser);
14 | router.get('/like-unlike/:id', checkToken, likeUnlikeUserId);
15 | // router.get('/:id', checkToken, getLikesByLikedUserId);
16 | // router.get('/:id', checkToken, getLikesByLikingUserId);
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/match/controller.js:
--------------------------------------------------------------------------------
1 | const Match = require('./model');
2 |
3 | const matchs = new Match();
4 |
5 | async function getMatchsFromCurrentUser(request, response) {
6 | try {
7 | const id = request.decoded.userid;
8 | const call = await matchs.getBy(['user1', 'user2'], id);
9 | response.status(200).json(call);
10 | } catch (err) {
11 | if (process.env.VERBOSE === 'true') console.log(err);
12 | response.status(206).send(err);
13 | }
14 | }
15 |
16 | async function getMatchById(request, response) {
17 | const id = parseInt(request.params.id, 10);
18 | try {
19 | const call = await matchs.getBy('id', id);
20 | response.status(200).json(call);
21 | } catch (err) {
22 | if (process.env.VERBOSE === 'true') console.log(err);
23 | response.status(206).send(err);
24 | }
25 | }
26 |
27 | module.exports.getMatchsFromCurrentUser = getMatchsFromCurrentUser;
28 | module.exports.getMatchById = getMatchById;
29 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/match/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const { getMatchsFromCurrentUser, getMatchById } = require('./controller');
6 |
7 | // list of all Matchs from current user - Match
8 | router.get('/', checkToken, getMatchsFromCurrentUser);
9 | // get Match by id - Match
10 | router.get('/:id', checkToken, getMatchById);
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/notification/controller.js:
--------------------------------------------------------------------------------
1 | const Notification = require('./model');
2 | const Match = require('./../match/model');
3 | const Block = require('./../block/model');
4 | const User = require('./../user/model');
5 | const Chat = require('./../chatroom/model');
6 | // const { sendNotificationEmail } = require('../../../mailer/sendNotificationEmail');
7 |
8 | const notifications = new Notification();
9 | const block = new Block();
10 | const matchs = new Match();
11 | const user = new User();
12 | const chat = new Chat();
13 |
14 | async function getNotificationsFromCurrentUser(request, response) {
15 | const id = request.decoded.userid;
16 | try {
17 | const call = await notifications.getBy('recipient', id);
18 | notifications.updateRead(id);
19 | response.status(200).json(call);
20 | } catch (err) {
21 | if (process.env.VERBOSE === 'true') console.log(err);
22 | response.status(206).send(err);
23 | }
24 | }
25 |
26 | async function numberOfUnreadNotifications(request, response) {
27 | const id = request.decoded.userid;
28 | try {
29 | const call = await notifications.numberUnread(id);
30 | response.status(200).json(call);
31 | } catch (err) {
32 | if (process.env.VERBOSE === 'true') console.log(err);
33 | response.status(206).send(err);
34 | }
35 | }
36 |
37 | module.exports.getNotificationsFromCurrentUser = getNotificationsFromCurrentUser;
38 | module.exports.numberOfUnreadNotifications = numberOfUnreadNotifications;
39 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/notification/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const {
6 | getNotificationsFromCurrentUser,
7 | numberOfUnreadNotifications,
8 | } = require('./controller');
9 |
10 | // get number of notifications unread from the current user
11 | router.get('/total', checkToken, numberOfUnreadNotifications);
12 | // get the list of Notification from the current user
13 | router.get('/', checkToken, getNotificationsFromCurrentUser);
14 |
15 | module.exports = router;
16 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/report/controller.js:
--------------------------------------------------------------------------------
1 | const Report = require('./model');
2 | const User = require('../user/model');
3 |
4 | const reports = new Report();
5 | const user = new User();
6 |
7 | async function reportUserId(request, response) {
8 | const reportingUser = request.decoded.userid;
9 | const reportedUser = parseInt(request.params.id, 10);
10 | if (reportedUser === reportingUser) {
11 | return response
12 | .status(200)
13 | .json({ success: false, error: 'You can not report yourself!' });
14 | }
15 | try {
16 | const alreadyReported = await reports.exists(reportingUser, reportedUser);
17 | let query;
18 | if (alreadyReported) {
19 | return response
20 | .status(200)
21 | .json({ created: false, message: 'You already reported this User' });
22 | }
23 | query = await reports.create(reportingUser, reportedUser);
24 | if (parseInt(query.nbOfReports, 10) >= 2) {
25 | user.updateById(reportedUser, { suspended: true });
26 | }
27 | return response.status(200).json(query);
28 | } catch (err) {
29 | if (process.env.VERBOSE === 'true') console.log(err);
30 | response.status(206).send(err);
31 | }
32 | }
33 |
34 | module.exports.reportUserId = reportUserId;
35 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/report/model.js:
--------------------------------------------------------------------------------
1 | const { db } = require('../../../config/database');
2 |
3 | class Report {
4 | isValidType(type) {
5 | const authorizedTypes = ['id', 'reportingUser', 'reportedUser'];
6 | return authorizedTypes.some(authorizedType => {
7 | return type === authorizedType;
8 | });
9 | }
10 |
11 | async create(reportingUserId, reportedUserId) {
12 | try {
13 | if (process.env.VERBOSE === 'true')
14 | console.log(
15 | `INSERT INTO public."Report" (reportingUser, reportedUser) VALUES (${reportingUserId}, ${reportedUserId} RETURNING id)`,
16 | );
17 | return await db
18 | .any(
19 | 'INSERT INTO public."Report" ("reportingUser", "reportedUser") VALUES ($1, $2) RETURNING id, (SELECT COUNT(*) as nbofreports FROM Public."Report" WHERE "reportedUser" = $2)',
20 | [reportingUserId, reportedUserId],
21 | )
22 | .then(data => {
23 | return {
24 | success: true,
25 | created: true,
26 | id: data[0].id,
27 | nbOfReports: data[0].nbofreports,
28 | };
29 | });
30 | } catch (err) {
31 | if (process.env.VERBOSE === 'true')
32 | console.log(err, 'in model Report.create()');
33 | return { created: false, error: err };
34 | }
35 | }
36 |
37 | async exists(reportingUser, reportedUser) {
38 | try {
39 | if (process.env.VERBOSE === 'true')
40 | console.log(
41 | `SELECT exists(SELECT from public."Report" WHERE "reportingUser" = ${reportingUser} AND "reportedUser" = ${reportedUser})`,
42 | );
43 | const result = await db.any(
44 | `SELECT exists(SELECT from public."Report" WHERE "reportingUser" = $1 AND "reportedUser" = $2);`,
45 | [reportingUser, reportedUser],
46 | );
47 | return result[0].exists;
48 | } catch (err) {
49 | if (process.env.VERBOSE === 'true')
50 | console.log(err, 'in model Report.exists()');
51 | return null;
52 | }
53 | }
54 | }
55 |
56 | module.exports = Report;
57 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/report/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const { reportUserId } = require('./controller');
6 |
7 | // get the list of blocked users from the current user
8 | // router.get('/', checkToken, getLikesFromCurrentUser);
9 | router.post('/:id', checkToken, reportUserId);
10 | module.exports = router;
11 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/sexualOrientation/controller.js:
--------------------------------------------------------------------------------
1 | const SexualOrientation = require('./model');
2 |
3 | const sexualOrientation = new SexualOrientation();
4 |
5 | async function getSexualOrientations(request, response) {
6 | try {
7 | const call = await sexualOrientation.getAll();
8 | response.status(200).json(call);
9 | } catch (err) {
10 | if (process.env.VERBOSE === 'true') console.log(err);
11 | response.status(206).send(err);
12 | }
13 | }
14 |
15 | async function getSexualOrientationById(request, response) {
16 | const id = parseInt(request.params.id, 10);
17 | try {
18 | const call = await sexualOrientation.getBy('id', id);
19 | response.status(200).json(call);
20 | } catch (err) {
21 | if (process.env.VERBOSE === 'true') console.log(err);
22 | response.status(206).send(err);
23 | }
24 | }
25 |
26 | module.exports.getSexualOrientations = getSexualOrientations;
27 | module.exports.getSexualOrientationById = getSexualOrientationById;
28 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/sexualOrientation/model.js:
--------------------------------------------------------------------------------
1 | const { db } = require('../../../config/database');
2 |
3 | class SexualOrientation {
4 | isValidType(type) {
5 | const authorizedTypes = ['id', 'name'];
6 | return authorizedTypes.some(authorizedType => {
7 | return type === authorizedType;
8 | });
9 | }
10 |
11 | async getBy(type, value) {
12 | try {
13 | if (!this.isValidType(type)) {
14 | if (process.env.VERBOSE === 'true')
15 | console.log(
16 | `SexualOrientation.getBy(): ${type} is not an authorized type`,
17 | );
18 | return null;
19 | }
20 | if (process.env.VERBOSE === 'true')
21 | console.log(
22 | `SELECT * FROM public."SexualOrientation" WHERE ${type} = ${value}`,
23 | );
24 | const result = await db.any(
25 | `SELECT * FROM public."Gender" WHERE $1:name = $2`,
26 | [type, value],
27 | );
28 | return result;
29 | } catch (err) {
30 | if (process.env.VERBOSE === 'true')
31 | console.log(err, 'in model Gender.getBy()');
32 | return null;
33 | }
34 | }
35 |
36 | async getAll() {
37 | try {
38 | if (process.env.VERBOSE === 'true')
39 | console.log('SELECT * FROM public."Gender"');
40 | const result = await db.any('SELECT * FROM public."Gender"');
41 | return result;
42 | } catch (err) {
43 | if (process.env.VERBOSE === 'true')
44 | console.log(err, 'in model Gender.getAll()');
45 | return null;
46 | }
47 | }
48 |
49 | async exists(type, value) {
50 | try {
51 | if (!value) return false;
52 | if (!this.isValidType(type)) {
53 | if (process.env.VERBOSE === 'true')
54 | console.log(`Gender.exists(): ${type} is not an authorized type`);
55 | return null;
56 | }
57 | if (process.env.VERBOSE === 'true')
58 | console.log(
59 | `SELECT exists(SELECT from public."Gender" WHERE ${type} = ${value})`,
60 | );
61 | const result = await db.any(
62 | `SELECT exists(SELECT from public."Gender" WHERE $1:name = $2);`,
63 | [type, value],
64 | );
65 | return result[0].exists;
66 | } catch (err) {
67 | if (process.env.VERBOSE === 'true')
68 | console.log(err, 'in model Gender.exists()');
69 | return null;
70 | }
71 | }
72 | }
73 |
74 | module.exports = SexualOrientation;
75 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/sexualOrientation/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const router = express.Router();
4 | const {
5 | getSexualOrientations,
6 | getSexualOrientationById,
7 | } = require('./controller');
8 |
9 | // list of all SexualOrientations - SexualOrientation
10 | router.get('/', getSexualOrientations);
11 | // get SexualOrientation by id - SexualOrientation
12 | router.get('/:id', getSexualOrientationById);
13 |
14 | module.exports = router;
15 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/user/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const {
6 | getUserById,
7 | getUserByUsername,
8 | getUsers,
9 | usernameExists,
10 | emailExists,
11 | createUser,
12 | updateUser,
13 | deleteUser,
14 | getMyUserInfo,
15 | search,
16 | suggestions,
17 | } = require('./controller');
18 |
19 | // list of all users - user
20 | router.get('/', checkToken, getUsers);
21 | // suggestions from the user
22 | router.post('/suggestions', checkToken, suggestions);
23 | // search for a user
24 | router.post('/search', checkToken, search);
25 | // username already exists ? - user
26 | router.get('/verification/username', usernameExists);
27 | // email already exists ? - user
28 | router.get('/verification/email', emailExists);
29 | // get profile of visited User
30 | router.get('/profile/:username', checkToken, getUserByUsername);
31 | // get my profile info
32 | router.get('/profile', checkToken, getMyUserInfo);
33 | // get user by id - user
34 | router.get('/:id', checkToken, getUserById);
35 | // create user - user
36 | router.post('/', createUser);
37 | // update user - user
38 | router.put('/', checkToken, updateUser);
39 | // delete user - user
40 | router.delete('/', checkToken, deleteUser);
41 |
42 | module.exports = router;
43 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/user/seed.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config({ path: './../../../../.env' });
2 | const faker = require('faker/locale/fr');
3 | const _ = require('lodash');
4 | const User = require('./model');
5 | const Interest = require('./../interests/model');
6 |
7 | const user = new User();
8 | const interest = new Interest();
9 | const axios = require('axios');
10 | const ObjectsToCsv = require('objects-to-csv');
11 |
12 | let interestsList = [];
13 |
14 | const createFakeUser = async () => {
15 | const firstName = faker.name.firstName();
16 | const surname = faker.name.lastName();
17 | const userName = firstName.toLowerCase() + faker.random.number();
18 | const password = faker.internet.password();
19 | const email = `${userName}@growth-tools.tk`;
20 |
21 | console.log(firstName, surname, userName, password, email);
22 | const newUser = {
23 | ...(await user.create({
24 | firstname: firstName,
25 | surname,
26 | username: userName,
27 | password,
28 | email,
29 | })),
30 | userName,
31 | password,
32 | email,
33 | };
34 | const csv = new ObjectsToCsv([newUser]);
35 | await csv.toDisk('./users.csv', { append: true });
36 | return newUser;
37 | };
38 |
39 | function randomInteger(min, max) {
40 | return Math.floor(Math.random() * (max - min + 1)) + min;
41 | }
42 | function randomArrayInt(min, max) {
43 | const array = [];
44 | array[0] = randomInteger(min, max);
45 | array[1] = randomInteger(min, max);
46 | array[2] = randomInteger(min, max);
47 | return _.sortBy(_.uniq(array));
48 | }
49 |
50 | function randomArrayOfInterests(nbOfElements) {
51 | const array = [];
52 | for (let i = 0; i < nbOfElements; i++) {
53 | array[i] = interestsList[Math.floor(Math.random() * interestsList.length)];
54 | }
55 | return _.sortBy(_.uniq(array));
56 | }
57 |
58 | const generateFakeImages = () => {
59 | return axios
60 | .get(
61 | 'https://api.generated.photos/api/v1/faces?api_key=0c_nmVH48EoxfDeTmn_-3Q&per_page=5&order_by=random',
62 | )
63 | .then(res => {
64 | return res.data.faces.map(face => {
65 | return face.urls[4]['512'];
66 | });
67 | });
68 | };
69 |
70 | const updateFakeUser = async userId => {
71 | const infos = {};
72 | infos.validated = true;
73 | infos.description = faker.lorem.paragraphs();
74 | infos.location = [];
75 | // latitude : entre 48.60 et 48.99
76 | infos.location[0] = parseFloat(`48.${randomInteger(60, 99)}`);
77 | // longitude : entre 2.30 et 2.60
78 | infos.location[1] = parseFloat(`2.${randomInteger(30, 60)}`);
79 | const now = new Date();
80 | infos.lastConnection = now.toISOString();
81 | infos.popularityRate = randomInteger(20, 90);
82 | infos.birthDate = faker.date.between('1940-01-01', '2001-12-31');
83 | infos.gender = randomArrayInt(1, 7);
84 | infos.sexualOrientation = randomArrayInt(1, 7);
85 | infos.interests = randomArrayOfInterests(10);
86 | infos.images = await generateFakeImages();
87 | infos.profilePicture = infos.images[0];
88 | await user.updateById(userId, infos);
89 | };
90 |
91 | const generateProfiles = async nbOfProfiles => {
92 | interestsList = await interest.getAll().then(list => {
93 | return list.map(element => element.name);
94 | });
95 | let profiles = [];
96 | for (let i = 0; i < nbOfProfiles; i++) {
97 | let userId = 0;
98 | const newUser = await createFakeUser();
99 | if (newUser.created) {
100 | userId = newUser.id;
101 | await updateFakeUser(userId);
102 | profiles.push(
103 | `new user created with id: ${userId} , username: ${newUser.userName} , password: ${newUser.password}`,
104 | );
105 | }
106 | }
107 | profiles.forEach(profile => console.log(profile));
108 | };
109 |
110 | generateProfiles(1);
111 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/userValidation/controller.js:
--------------------------------------------------------------------------------
1 | const UserValidation = require('./model');
2 | const {
3 | sendForgotPasswordEmail,
4 | } = require('../../../mailer/sendForgotPasswordEmail');
5 | const User = require('../user/model');
6 | const bcrypt = require('bcrypt');
7 |
8 | const user = new User();
9 | const uv = new UserValidation();
10 |
11 | async function verifyConfirmationToken(request, response) {
12 | try {
13 | const call = await uv.verifyConfirmationToken({
14 | token: request.params.token,
15 | });
16 | response.status(200).json(call);
17 | } catch (err) {
18 | if (process.env.VERBOSE === 'true') console.log(err);
19 | response.status(206).send(err);
20 | }
21 | }
22 |
23 | async function verifyForgotPasswordToken(request, response) {
24 | try {
25 | const call = await uv.verifyForgotPasswordToken({
26 | token: request.params.token,
27 | });
28 | response.status(200).json(call);
29 | } catch (err) {
30 | if (process.env.VERBOSE === 'true') console.log(err);
31 | response.status(206).send(err);
32 | }
33 | }
34 |
35 | async function forgotPassword(request, response) {
36 | const { email } = request.body;
37 | try {
38 | const userRequest = await user.getBy('email', email);
39 | if (userRequest.length) {
40 | const { id, firstname } = userRequest[0];
41 | if (process.env.VERBOSE === 'true') console.log(id);
42 | const call = await uv.create({ userId: id, type: 'resetPassword' });
43 | if (call.created) {
44 | sendForgotPasswordEmail(email, firstname, call.token);
45 | response.status(206).send({ success: true });
46 | } else {
47 | response.status(206).send({ success: false, err: call });
48 | }
49 | } else {
50 | response.status(200).json({
51 | success: false,
52 | err: "This email doesn't exist",
53 | });
54 | }
55 | } catch (err) {
56 | if (process.env.VERBOSE === 'true') console.log(err);
57 | response.status(206).send(err);
58 | }
59 | }
60 |
61 | async function forgotPasswordUpdate(request, response) {
62 | const { password } = request.body;
63 | try {
64 | const call = await uv.verifyForgotPasswordToken({
65 | token: request.params.token,
66 | });
67 | if (process.env.VERBOSE === 'true') console.log(call);
68 | const { success, userId } = call;
69 | if (success) {
70 | const hashedPassword = bcrypt.hashSync(password, 10);
71 | await user.updateById(userId, { password: hashedPassword });
72 | uv.delete({ userId });
73 | response.status(206).send({ success: true });
74 | } else {
75 | response.status(206).send({ success: false, err: call.error });
76 | }
77 | } catch (err) {
78 | if (process.env.VERBOSE === 'true') console.log(err);
79 | response.status(206).send(err);
80 | }
81 | }
82 |
83 | module.exports.verifyConfirmationToken = verifyConfirmationToken;
84 | module.exports.forgotPassword = forgotPassword;
85 | module.exports.verifyForgotPasswordToken = verifyForgotPasswordToken;
86 | module.exports.forgotPasswordUpdate = forgotPasswordUpdate;
87 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/userValidation/model.js:
--------------------------------------------------------------------------------
1 | const TokenGenerator = require('uuid-token-generator');
2 | const { db } = require('../../../config/database');
3 | const User = require('../user/model');
4 |
5 | const user = new User();
6 |
7 | const tokgen = new TokenGenerator(256, TokenGenerator.BASE62);
8 |
9 | class UserValidation {
10 | async create({ userId, type }) {
11 | if (type !== 'validationKey' && type !== 'resetPassword') {
12 | if (process.env.VERBOSE === 'true')
13 | console.log('The type is not valid in model UserValidation.create()');
14 | return {
15 | created: false,
16 | error: 'The type is not valid in model UserValidation.create()',
17 | };
18 | }
19 | try {
20 | const token = tokgen.generate();
21 | if (process.env.VERBOSE === 'true')
22 | console.log(
23 | `INSERT INTO public."UserValidation" (userId, ${type}) VALUES (${userId}, ${token})`,
24 | );
25 | // await db.any(
26 | // 'INSERT INTO public."UserValidation" ("userId", $1:name) VALUES ($2, $3)',
27 | // [type, userId, token],
28 | // );
29 | await db.any(
30 | 'UPDATE public."UserValidation" SET $1:name=$3 WHERE "userId"=$2; INSERT INTO public."UserValidation" ("userId", $1:name) SELECT $2, $3 WHERE NOT EXISTS (SELECT 1 FROM public."UserValidation" WHERE "userId"=$2)',
31 | [type, userId, token],
32 | );
33 | return { created: true, token };
34 | } catch (err) {
35 | if (process.env.VERBOSE === 'true')
36 | console.log(err, 'in model UserValidation.create()');
37 | return { created: false, error: err };
38 | }
39 | }
40 |
41 | async verifyConfirmationToken({ token }) {
42 | if (token === undefined) {
43 | if (process.env.VERBOSE === 'true')
44 | console.log('The token is not defined');
45 | return {
46 | success: false,
47 | error: 'The token is not defined',
48 | };
49 | }
50 | try {
51 | return db
52 | .one(
53 | 'DELETE FROM public."UserValidation" WHERE "validationKey" = $1 RETURNING "userId"',
54 | token,
55 | )
56 | .then(({ userId }) => {
57 | user.updateById(userId, { validated: true });
58 | return {
59 | success: true,
60 | error: 'The account is now validated!',
61 | };
62 | })
63 | .catch(error => {
64 | if (error.received === 0) {
65 | return {
66 | success: false,
67 | error: 'The confirmation link is not valid',
68 | };
69 | }
70 | if (process.env.VERBOSE === 'true')
71 | console.log(error, 'in model UserValidation.create()');
72 | return {
73 | success: false,
74 | error,
75 | };
76 | });
77 | } catch (err) {
78 | if (process.env.VERBOSE === 'true')
79 | console.log(err, 'in model UserValidation.create()');
80 | return { success: false, error: err };
81 | }
82 | }
83 |
84 | async verifyForgotPasswordToken({ token }) {
85 | if (token === undefined) {
86 | if (process.env.VERBOSE === 'true')
87 | console.log('The token is not defined');
88 | return {
89 | success: false,
90 | error: 'The token is not defined',
91 | };
92 | }
93 | try {
94 | return db
95 | .one(
96 | 'SELECT * FROM public."UserValidation" WHERE "resetPassword" = $1',
97 | token,
98 | )
99 | .then(data => {
100 | return {
101 | success: true,
102 | userId: data.userId,
103 | };
104 | })
105 | .catch(error => {
106 | if (error.received === 0) {
107 | return {
108 | success: false,
109 | error: 'The confirmation link is not valid',
110 | };
111 | }
112 | if (process.env.VERBOSE === 'true')
113 | console.log(error, 'in model UserValidation.create()');
114 | return {
115 | success: false,
116 | error,
117 | };
118 | });
119 | } catch (err) {
120 | if (process.env.VERBOSE === 'true')
121 | console.log(err, 'in model UserValidation.create()');
122 | return { success: false, error: err };
123 | }
124 | }
125 |
126 | async delete({ userId }) {
127 | try {
128 | db.any('DELETE FROM public."UserValidation" WHERE "userId" = $1', userId);
129 | return;
130 | } catch (error) {
131 | if (process.env.VERBOSE === 'true')
132 | console.log(error, 'in model UserValidation.delete()');
133 | return { success: false, error };
134 | }
135 | }
136 | }
137 | module.exports = UserValidation;
138 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/userValidation/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 |
3 | const router = express.Router();
4 | const {
5 | verifyConfirmationToken,
6 | verifyForgotPasswordToken,
7 | forgotPassword,
8 | forgotPasswordUpdate,
9 | } = require('./controller');
10 |
11 | // confirm new account with token
12 | router.get('/newaccount/:token', verifyConfirmationToken);
13 | router.get('/forgotpassword/:token', verifyForgotPasswordToken);
14 | router.post('/forgotpasswordcreate', forgotPassword);
15 | router.post('/forgotpasswordupdate/:token', forgotPasswordUpdate);
16 |
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/visit/controller.js:
--------------------------------------------------------------------------------
1 | const Visit = require('./model');
2 | const _ = require('lodash');
3 | const { isConnected } = require('./../../../socket/newConnection');
4 | const visits = new Visit();
5 |
6 | async function getVisits(request, response) {
7 | try {
8 | const call = await visits.getAll();
9 | response.status(200).json(call);
10 | } catch (err) {
11 | if (process.env.VERBOSE === 'true') console.log(err);
12 | response.status(206).send(err);
13 | }
14 | }
15 |
16 | async function getVisitsFromCurrentUser(request, response) {
17 | const id = request.decoded.userid;
18 | try {
19 | let call = await visits.getBy('visited', id);
20 | call = _.uniqBy(call, 'visitor');
21 | call = _.map(call, visit => {
22 | return { ...visit, connected: isConnected(visit.visitor) };
23 | });
24 | response.status(200).json(call);
25 | } catch (err) {
26 | if (process.env.VERBOSE === 'true') console.log(err);
27 | response.status(206).send(err);
28 | }
29 | }
30 |
31 | module.exports.getVisits = getVisits;
32 | module.exports.getVisitsFromCurrentUser = getVisitsFromCurrentUser;
33 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/visit/model.js:
--------------------------------------------------------------------------------
1 | const { db } = require('../../../config/database');
2 |
3 | class Visit {
4 | isValidType(type) {
5 | const authorizedTypes = ['id', 'visitor', 'visited'];
6 | return authorizedTypes.some(authorizedType => {
7 | return type === authorizedType;
8 | });
9 | }
10 |
11 | async create(visitorId, visitedId) {
12 | try {
13 | if (process.env.VERBOSE === 'true')
14 | console.log(
15 | `INSERT INTO public."Visit" (visitor, visited, date) VALUES (${visitorId}, ${visitedId} RETURNING id)`,
16 | );
17 | return await db
18 | .any(
19 | 'INSERT INTO public."Visit" (visitor, visited, date) VALUES ($1, $2, NOW()) RETURNING id',
20 | [visitorId, visitedId],
21 | )
22 | .then(data => {
23 | return { created: true, id: data[0].id };
24 | });
25 | } catch (err) {
26 | if (process.env.VERBOSE === 'true')
27 | console.log(err, 'in model Visit.create()');
28 | return { created: false, error: err };
29 | }
30 | }
31 |
32 | async getBy(type, value) {
33 | try {
34 | if (!this.isValidType(type)) {
35 | if (process.env.VERBOSE === 'true')
36 | console.log(`Visit.getBy(): ${type} is not an authorized type`);
37 | return null;
38 | }
39 | if (process.env.VERBOSE === 'true')
40 | console.log(
41 | `SELECT firstname, username, birthDate, location, popularityRate, profilePicture, date FROM public."Visit" WHERE ${type} = ${value}`,
42 | );
43 | const result = await db.any(
44 | `SELECT firstname, username, "birthDate", location, "popularityRate", "profilePicture", date, visitor,
45 | EXISTS(SELECT * FROM public."Like" WHERE "likingUser" = $2 AND "likedUser" = visitor) AS liking,
46 | EXISTS(SELECT * FROM public."Like" WHERE "likedUser" = $2 AND "likingUser" = visitor) AS liked
47 | FROM public."Visit", public."User"
48 | WHERE $1:name = $2
49 | AND "Visit".visitor = "User".id
50 | AND NOT EXISTS (
51 | SELECT *
52 | FROM public."Block"
53 | WHERE "blockedUser" = $2
54 | AND "blockingUser" = visitor
55 | )
56 | ORDER BY date DESC`,
57 | [type, value],
58 | );
59 | result.forEach(element => {
60 | element.match = element.liked && element.liking;
61 | });
62 | return result;
63 | } catch (err) {
64 | if (process.env.VERBOSE === 'true')
65 | console.log(err, 'in model Visit.getBy()');
66 | return null;
67 | }
68 | }
69 |
70 | async getAll() {
71 | try {
72 | if (process.env.VERBOSE === 'true')
73 | console.log('SELECT * FROM public."Visit"');
74 | const result = await db.any('SELECT * FROM public."Visit"');
75 | return result;
76 | } catch (err) {
77 | if (process.env.VERBOSE === 'true')
78 | console.log(err, 'in model Visit.getAll()');
79 | return null;
80 | }
81 | }
82 |
83 | async exists(type, value) {
84 | try {
85 | if (!value) return false;
86 | if (!this.isValidType(type)) {
87 | if (process.env.VERBOSE === 'true')
88 | console.log(`Visit.exists(): ${type} is not an authorized type`);
89 | return null;
90 | }
91 | if (process.env.VERBOSE === 'true')
92 | console.log(
93 | `SELECT exists(SELECT from public."Visit" WHERE ${type} = ${value})`,
94 | );
95 | const result = await db.none(
96 | `SELECT exists(SELECT from public."Visit" WHERE id = ALL($2));`,
97 | [value],
98 | );
99 | return result[0].exists;
100 | } catch (err) {
101 | if (process.env.VERBOSE === 'true')
102 | console.log(err, 'in model Visit.exists()');
103 | return null;
104 | }
105 | }
106 | }
107 |
108 | module.exports = Visit;
109 |
--------------------------------------------------------------------------------
/server/rest/components(C-M-R)/visit/routes.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { checkToken } = require('../../middleware/jwt');
3 |
4 | const router = express.Router();
5 | const { getVisits, getVisitsFromCurrentUser } = require('./controller');
6 |
7 | // get the list of visit from the current user
8 | router.get('/', checkToken, getVisitsFromCurrentUser);
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/server/rest/middleware/jwt.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 | // var bcrypt = require('bcrypt');
3 | const secret = 'mignon4ever';
4 |
5 | async function checkToken(request, response, next) {
6 | let token =
7 | request.headers['x-access-token'] || request.headers.authorization;
8 | if (token) {
9 | if (token.startsWith('Bearer ')) {
10 | token = token.slice(7, token.length);
11 | }
12 | jwt.verify(token, secret, (err, decoded) => {
13 | if (err) {
14 | return response.json({
15 | success: false,
16 | message: 'Token is not valid',
17 | });
18 | }
19 | request.decoded = decoded;
20 | next();
21 | return request.decoded;
22 | });
23 | } else {
24 | return response.json({
25 | success: false,
26 | message: 'Auth token not supplied',
27 | });
28 | }
29 | return null;
30 | }
31 |
32 | async function checkTokenSocket(token) {
33 | if (token) {
34 | if (token.startsWith('Bearer ')) {
35 | token = token.slice(7, token.length);
36 | }
37 | return jwt.verify(token, secret, (err, decoded) => {
38 | if (err) {
39 | return false;
40 | }
41 | return decoded;
42 | });
43 | }
44 | }
45 |
46 | module.exports.checkToken = checkToken;
47 | module.exports.checkTokenSocket = checkTokenSocket;
48 |
--------------------------------------------------------------------------------
/server/socket/connectedUsers.js:
--------------------------------------------------------------------------------
1 | let connectedUsers = {};
2 |
3 | const getConnectedUsers = () => {
4 | return connectedUsers;
5 | };
6 |
7 | const setConnectedUsers = value => {
8 | connectedUsers = value;
9 | return connectedUsers;
10 | };
11 |
12 | module.exports.getConnectedUsers = getConnectedUsers;
13 | module.exports.setConnectedUsers = setConnectedUsers;
14 |
--------------------------------------------------------------------------------
/server/socket/disconnection.js:
--------------------------------------------------------------------------------
1 | const { setConnectedUsers } = require('./connectedUsers');
2 |
3 | const disconnection = (io, connectedUsers, socket) => {
4 | if (process.env.VERBOSE === 'true')
5 | console.log('user disconnected', connectedUsers[socket.id]);
6 | socket.removeAllListeners('chat message');
7 | socket.removeAllListeners('joinchatroom');
8 | socket.removeAllListeners('disconnect');
9 | // io.removeAllListeners('connection');
10 | delete connectedUsers[socket.id];
11 | setConnectedUsers(connectedUsers);
12 | };
13 |
14 | module.exports = disconnection;
15 |
--------------------------------------------------------------------------------
/server/socket/joinChatroom.js:
--------------------------------------------------------------------------------
1 | const Chat = require('./../rest/components(C-M-R)/chatroom/model');
2 | const chat = new Chat();
3 |
4 | const joinChatroom = async (matchId, userId, socket) => {
5 | if (await chat.userCanAccessMatch(matchId, userId)) {
6 | socket.join(matchId);
7 | } else {
8 | if (process.env.VERBOSE === 'true')
9 | console.log(
10 | `The user ${userId} cannot access this chatroom because it doesn't belong to match ${matchId}`,
11 | );
12 | }
13 | };
14 |
15 | module.exports = joinChatroom;
16 |
--------------------------------------------------------------------------------
/server/socket/newConnection.js:
--------------------------------------------------------------------------------
1 | const { checkTokenSocket } = require('./../rest/middleware/jwt');
2 | const _ = require('lodash');
3 | const { getConnectedUsers, setConnectedUsers } = require('./connectedUsers');
4 |
5 | const connectedUsersId = () => {
6 | const connectedUsers = getConnectedUsers();
7 | return _.uniq(_.values(connectedUsers));
8 | };
9 |
10 | const isConnected = id => {
11 | return _.includes(connectedUsersId(), id);
12 | };
13 |
14 | const newConnection = async (io, socket) => {
15 | const userConnected = await checkTokenSocket(socket.handshake.query.token);
16 | if (userConnected) {
17 | const connectedUsers = getConnectedUsers();
18 | connectedUsers[socket.id] = userConnected.userid;
19 | setConnectedUsers(connectedUsers);
20 | }
21 | };
22 |
23 | module.exports.newConnection = newConnection;
24 | module.exports.connectedUsersId = connectedUsersId;
25 | module.exports.isConnected = isConnected;
26 |
--------------------------------------------------------------------------------
/server/socket/newMessage.js:
--------------------------------------------------------------------------------
1 | const Chat = require('./../rest/components(C-M-R)/chatroom/model');
2 | const Match = require('./../rest/components(C-M-R)/match/model');
3 | const Block = require('./../rest/components(C-M-R)/block/model');
4 | const newNotification = require('./newNotification');
5 |
6 | const chat = new Chat();
7 | const match = new Match();
8 | const block = new Block();
9 |
10 | const newMessage = async (msg, matchId, userId, socket, io) => {
11 | if (await chat.canAccessChat(matchId)) {
12 | chat.create(matchId, userId, msg).then(async response => {
13 | io.to(matchId).emit('chat message', response);
14 | match.updateLastMessage(matchId, response.id);
15 | const matchUsers = await match.getUsersFromMatchId(matchId);
16 | let recipient;
17 | if (matchUsers[0] === userId) {
18 | [, recipient] = matchUsers;
19 | } else {
20 | [recipient] = matchUsers;
21 | }
22 | newNotification(recipient, userId, 'message');
23 | });
24 | } else {
25 | socket.emit('redirect', 'cant_access_chat');
26 | }
27 | };
28 |
29 | module.exports = newMessage;
30 |
--------------------------------------------------------------------------------
/server/socket/newNotification.js:
--------------------------------------------------------------------------------
1 | const { getConnectedUsers, setConnectedUsers } = require('./connectedUsers');
2 | const _ = require('lodash');
3 | const Notification = require('./../rest/components(C-M-R)/notification/model');
4 | const notification = new Notification();
5 |
6 | const newNotification = (recipient, sender, type) => {
7 | const connectedUsers = getConnectedUsers();
8 | const recipientSocketIds = _.keys(
9 | _.pickBy(connectedUsers, user => {
10 | return user === recipient;
11 | }),
12 | );
13 | if (process.env.VERBOSE === 'true')
14 | console.log('recipientSocketIds', recipientSocketIds);
15 | notification.create(recipient, sender, type);
16 | recipientSocketIds.forEach(socketId => {
17 | io.to(socketId).emit('new notification', sender, type);
18 | });
19 | };
20 |
21 | module.exports = newNotification;
22 |
--------------------------------------------------------------------------------
/server/socket/socket.js:
--------------------------------------------------------------------------------
1 | const { newConnection } = require('./newConnection');
2 | const { getConnectedUsers, setConnectedUsers } = require('./connectedUsers');
3 | const disconnection = require('./disconnection');
4 | const newMessage = require('./newMessage');
5 | const joinChatroom = require('./joinChatroom');
6 |
7 | const socketRouter = () => {
8 | io.on('connection', async socket => {
9 | newConnection(io, socket, getConnectedUsers());
10 | socket.on('joinchatroom', function(match) {
11 | joinChatroom(match, getConnectedUsers()[socket.id], socket);
12 | });
13 | socket.on('error', function(err) {
14 | if (process.env.VERBOSE === 'true') console.log(err.stack);
15 | });
16 | socket.on('disconnect', function() {
17 | disconnection(io, getConnectedUsers(), socket);
18 | });
19 |
20 | socket.on('chat message', function(msg, match) {
21 | if (process.env.VERBOSE === 'true')
22 | console.log('new msg', msg, match, getConnectedUsers()[socket.id]);
23 | newMessage(msg, match, getConnectedUsers()[socket.id], socket, io);
24 | });
25 | });
26 | };
27 |
28 | module.exports = socketRouter;
29 |
--------------------------------------------------------------------------------
/src/assets/images/heart-bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/heart-bg.jpg
--------------------------------------------------------------------------------
/src/assets/images/heart-confetti-background-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/heart-confetti-background-1.png
--------------------------------------------------------------------------------
/src/assets/images/home-bg-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/home-bg-1.jpg
--------------------------------------------------------------------------------
/src/assets/images/home-bg-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/home-bg-2.jpg
--------------------------------------------------------------------------------
/src/assets/images/home-bg-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/home-bg-3.jpg
--------------------------------------------------------------------------------
/src/assets/images/mamie.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/mamie.jpeg
--------------------------------------------------------------------------------
/src/assets/images/pink-bg-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/pink-bg-1.jpg
--------------------------------------------------------------------------------
/src/assets/images/pink-bg-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/pink-bg-2.jpg
--------------------------------------------------------------------------------
/src/assets/images/pink-bg-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/assets/images/pink-bg-3.jpg
--------------------------------------------------------------------------------
/src/components/ResetforgotPassword/index.js:
--------------------------------------------------------------------------------
1 | import ResetForgotPassword from './resetforgotpassword-view';
2 |
3 | export default ResetForgotPassword;
4 |
--------------------------------------------------------------------------------
/src/components/ResetforgotPassword/resetforgotpassword-container.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import axios from 'axios';
3 | import { toast } from 'react-toastify';
4 |
5 | const usePasswordForm = (callback, token) => {
6 | const [inputs, setInputs] = useState({
7 | password1: '',
8 | password2: '',
9 | });
10 | const { password1, password2 } = inputs;
11 |
12 | const handleSubmit = event => {
13 | if (event) {
14 | event.preventDefault();
15 | if (password1 !== password2) {
16 | toast.error("The passwords doesn't match");
17 | return;
18 | }
19 | axios
20 | .post(
21 | `${process.env.REACT_APP_PUBLIC_API_URL}/validation/forgotpasswordupdate/${token}`,
22 | {
23 | password: password1,
24 | },
25 | {
26 | headers: {
27 | 'Content-type': 'application/json; charset=UTF-8',
28 | },
29 | },
30 | )
31 | .then(({ data }) => {
32 | if (data.success === true) {
33 | callback(true);
34 | } else {
35 | toast.error(data.err);
36 | }
37 | });
38 | }
39 | };
40 |
41 | const handleInputChange = event => {
42 | event.persist();
43 | const newInput = {
44 | ...inputs,
45 | [event.target.name]: event.target.value,
46 | };
47 | setInputs(newInput);
48 | };
49 | return {
50 | handleSubmit,
51 | handleInputChange,
52 | inputs,
53 | };
54 | };
55 |
56 | export default usePasswordForm;
57 |
--------------------------------------------------------------------------------
/src/components/ResetforgotPassword/resetforgotpassword-view.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import React, { useState } from 'react';
3 | import PropTypes from 'prop-types';
4 | import Avatar from '@material-ui/core/Avatar';
5 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
6 | import TextField from '@material-ui/core/TextField';
7 | import Button from '@material-ui/core/Button';
8 | import Container from '@material-ui/core/Container';
9 | import Typography from '@material-ui/core/Typography';
10 | import { makeStyles } from '@material-ui/core/styles';
11 | import usePasswordForm from './resetforgotpassword-container';
12 |
13 | const useStyles = makeStyles(theme => ({
14 | '@global': {
15 | body: {
16 | backgroundColor: theme.palette.common.white,
17 | },
18 | },
19 | paper: {
20 | marginTop: theme.spacing(8),
21 | display: 'flex',
22 | flexDirection: 'column',
23 | alignItems: 'center',
24 | },
25 | avatar: {
26 | margin: theme.spacing(1),
27 | backgroundColor: theme.palette.secondary.main,
28 | },
29 | form: {
30 | width: '100%',
31 | marginTop: theme.spacing(1),
32 | },
33 | submit: {
34 | margin: theme.spacing(3, 0, 2),
35 | },
36 | }));
37 |
38 | const ResetForgotPassword = ({ computedMatch }) => {
39 | const classes = useStyles();
40 | const callback = success => {
41 | if (success) {
42 | window.location = '/?message=reset_password_success';
43 | }
44 | };
45 | const { token } = computedMatch.params;
46 | const { inputs, handleInputChange, handleSubmit } = usePasswordForm(
47 | callback,
48 | token,
49 | );
50 | const [validToken, setValidToken] = useState(false);
51 | if (!validToken) {
52 | axios
53 | .get(
54 | `${process.env.REACT_APP_PUBLIC_API_URL}/validation/forgotpassword/${token}`,
55 | {
56 | headers: {
57 | 'Content-type': 'application/json; charset=UTF-8',
58 | },
59 | },
60 | )
61 | .then(data => {
62 | if (data.data.success) {
63 | setValidToken(true);
64 | } else {
65 | window.location = '/?message=user_not_validated';
66 | }
67 | });
68 | }
69 | if (validToken === true) {
70 | return (
71 |
72 |
73 |
74 |
75 |
76 |
77 | Change your password
78 |
79 |
118 |
119 |
120 | );
121 | }
122 | return Chargement en cours ;
123 | };
124 | ResetForgotPassword.propTypes = {
125 | // eslint-disable-next-line react/forbid-prop-types
126 | computedMatch: PropTypes.object.isRequired,
127 | };
128 |
129 | export default ResetForgotPassword;
130 |
--------------------------------------------------------------------------------
/src/components/app/App.css:
--------------------------------------------------------------------------------
1 | /* .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | }
8 |
9 | .App-header {
10 | background-color: #282c34;
11 | min-height: 100vh;
12 | display: flex;
13 | flex-direction: column;
14 | align-items: center;
15 | justify-content: center;
16 | font-size: calc(10px + 2vmin);
17 | color: white;
18 | }
19 |
20 | .App-link {
21 | color: #09d3ac;
22 | } */
23 | /*
24 | body {
25 | font-family: 'roboto';
26 | margin: 0px;
27 | } */
28 |
--------------------------------------------------------------------------------
/src/components/app/App.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
2 | import { ToastContainer } from 'react-toastify';
3 | import React from 'react';
4 | import Signup from '../signup';
5 | import './App.css';
6 | import Login from '../login';
7 | import Profile from '../profile';
8 | import ProfileShow from '../profileshow';
9 | import Search from '../search';
10 | import Suggestions from '../suggestions';
11 | import Visit from '../visit';
12 | import Like from '../like';
13 | import Home from '../home';
14 | import { AuthProvider } from './AuthContext';
15 | import SecureRoute from './SecureRoute';
16 | import NotLoggedRoute from './NotLoggedRoute';
17 | import 'react-toastify/dist/ReactToastify.css';
18 | import Nav from '../nav';
19 | import Toaster from '../toaster';
20 | import UserValidation from '../uservalidation';
21 | import ResetForgotPassword from '../ResetforgotPassword';
22 | import ForgotPassword from '../forgotpassword';
23 | import Chat from '../chat';
24 | import ChatRoom from '../chatroom';
25 |
26 | function App() {
27 | return (
28 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | );
58 | }
59 |
60 | export default App;
61 |
--------------------------------------------------------------------------------
/src/components/app/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/app/AuthContext.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useState } from 'react';
2 | import PropTypes from 'prop-types';
3 | import io from 'socket.io-client';
4 | import { getToken, getUserData } from '../auth/AuthContainer';
5 |
6 | export const AuthContext = createContext();
7 |
8 | export const AuthProvider = ({ children }) => {
9 | const currentToken = getToken();
10 | const [token] = useState(currentToken);
11 | const [userData] = useState(getUserData(currentToken));
12 |
13 | const authContext = {
14 | token,
15 | userData,
16 | };
17 |
18 | const socket = io(`${process.env.REACT_APP_PUBLIC_API_URL}`, {
19 | query: { token },
20 | });
21 | const socketContext = { socket };
22 |
23 | return (
24 |
30 | {children}
31 |
32 | );
33 | };
34 | AuthProvider.propTypes = {
35 | // eslint-disable-next-line react/forbid-prop-types
36 | children: PropTypes.array.isRequired,
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/app/NotLoggedRoute.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React, { useState, useContext } from 'react';
3 | import PropTypes from 'prop-types';
4 | import { AuthContext } from './AuthContext';
5 | import { isTokenExpired } from '../auth/AuthContainer';
6 |
7 | const NotLoggedRoute = ({ component: Component, ...rest }) => {
8 | const { authContext } = useContext(AuthContext);
9 | const defaultValue =
10 | authContext.token !== null && !isTokenExpired(authContext.token);
11 | const [secureAuth, setSecureAuth] = useState(defaultValue);
12 | if (secureAuth === true) {
13 | authContext.userData.then(data => setSecureAuth(data.success));
14 | window.location = '/?message=already_loggedin';
15 | }
16 | return ;
17 | };
18 |
19 | NotLoggedRoute.propTypes = {
20 | component: PropTypes.func.isRequired,
21 | };
22 |
23 | export default NotLoggedRoute;
24 |
--------------------------------------------------------------------------------
/src/components/app/SecureRoute.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React, { useState, useContext } from 'react';
3 | import { Redirect, Route } from 'react-router-dom';
4 | import PropTypes from 'prop-types';
5 | import { AuthContext } from './AuthContext';
6 |
7 | const SecureRoute = ({ component: Component, ...rest }) => {
8 | const { authContext } = useContext(AuthContext);
9 | const defaultValue = authContext.token !== null;
10 | const [secureAuth, setSecureAuth] = useState(defaultValue);
11 | if (secureAuth === true) {
12 | authContext.userData.then(data => {
13 | if (data === null) {
14 | window.location = '/login';
15 | }
16 | setSecureAuth(data.success);
17 | });
18 | }
19 |
20 | return (
21 | {
24 | if (secureAuth) {
25 | return ;
26 | }
27 | return ;
28 | }}
29 | />
30 | );
31 | };
32 |
33 | SecureRoute.propTypes = {
34 | component: PropTypes.func.isRequired,
35 | };
36 | export default SecureRoute;
37 |
--------------------------------------------------------------------------------
/src/components/app/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | export default App;
4 |
--------------------------------------------------------------------------------
/src/components/auth/AuthContainer.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-console */
2 | import decode from 'jwt-decode';
3 | import axios from 'axios';
4 |
5 | export const getToken = () => {
6 | return localStorage.getItem('token');
7 | };
8 |
9 | export const isTokenExpired = token => {
10 | const currentTime = Date.now() / 1000;
11 | try {
12 | const decoded = decode(token);
13 | if (decoded.exp < currentTime) {
14 | if (process.env.REACT_APP_VERBOSE === 'true')
15 | console.log('Your token is expired');
16 | return true;
17 | }
18 | return false;
19 | } catch (err) {
20 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(err);
21 | return true;
22 | }
23 | };
24 |
25 | export const getUserData = async token => {
26 | if (!token || isTokenExpired(token)) {
27 | return null;
28 | }
29 | const userData = await axios.get(
30 | `${process.env.REACT_APP_PUBLIC_API_URL}/auth/checkToken`,
31 | {
32 | headers: {
33 | 'Content-type': 'application/json; charset=UTF-8',
34 | 'x-access-token': token,
35 | },
36 | },
37 | );
38 | return userData.data;
39 | };
40 |
41 | export const logout = (e, setIsLoggedIn) => {
42 | e.preventDefault();
43 | localStorage.removeItem('token');
44 | setIsLoggedIn(false);
45 | window.location = '/?message=logout_success';
46 | };
47 |
--------------------------------------------------------------------------------
/src/components/auth/index.js:
--------------------------------------------------------------------------------
1 | import { getToken, isTokenExpired, getUserData, logout } from './AuthContainer';
2 |
3 | export { getToken, getUserData, isTokenExpired, logout };
4 |
--------------------------------------------------------------------------------
/src/components/chat/chat-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState, useContext } from 'react';
3 | import _ from 'lodash';
4 | import { AuthContext } from '../app/AuthContext';
5 |
6 | const ChatContainer = () => {
7 | const [loaded, setLoaded] = useState(false);
8 | const { authContext } = useContext(AuthContext);
9 | const { token } = authContext;
10 | const [matchList, setMatchList] = useState({});
11 |
12 | const fetchCurrentUserMatches = () => {
13 | axios
14 | .get(`${process.env.REACT_APP_PUBLIC_API_URL}/chat/`, {
15 | headers: {
16 | 'Content-type': 'application/json; charset=UTF-8',
17 | 'x-access-token': token,
18 | },
19 | })
20 | .then(response => {
21 | setMatchList(response.data);
22 | return response.data;
23 | })
24 | .catch(error => {
25 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
26 | });
27 | };
28 |
29 | if (_.isEmpty(matchList) && loaded === false) {
30 | fetchCurrentUserMatches();
31 | setLoaded(true);
32 | }
33 | return { matchList };
34 | };
35 |
36 | export default ChatContainer;
37 |
--------------------------------------------------------------------------------
/src/components/chat/chat-view.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import Button from '@material-ui/core/Button';
4 | import Avatar from '@material-ui/core/Avatar';
5 | import List from '@material-ui/core/List';
6 | import ListItem from '@material-ui/core/ListItem';
7 | import Divider from '@material-ui/core/Divider';
8 | import Box from '@material-ui/core/Box';
9 | import ListItemText from '@material-ui/core/ListItemText';
10 | import ListItemAvatar from '@material-ui/core/ListItemAvatar';
11 | import _ from 'lodash';
12 | import ChatContainer from './chat-container';
13 |
14 | const useStyles = makeStyles(theme => ({
15 | buttonChatroom: {
16 | width: '100%',
17 | height: '100%',
18 | textTransform: 'none',
19 | padding: '0',
20 | },
21 | notReadMessage: {
22 | backgroundColor: '#edf2fa',
23 | },
24 | emptyChat: {
25 | display: 'flex',
26 | flexDirection: 'column',
27 | justifyContent: 'center',
28 | alignItems: 'center',
29 | },
30 | emptyChatTitle: {
31 | margin: theme.spacing(2),
32 | },
33 | }));
34 |
35 | const Chat = () => {
36 | const classes = useStyles();
37 | const { matchList } = ChatContainer();
38 | if (_.isEmpty(matchList)) {
39 | return (
40 |
41 |
42 | You don't have matches yet. But don't worry, it's coming😌
43 | {' '}
44 |
45 |
46 | );
47 | }
48 | return (
49 |
50 | {_.map(matchList, matchedProfile => {
51 | const redirectLink = `/chatroom/${matchedProfile.matchid}`;
52 | return (
53 |
54 |
59 |
65 |
66 |
67 |
68 |
72 |
73 |
74 |
75 |
76 | );
77 | })}
78 |
79 | );
80 | };
81 |
82 | export default Chat;
83 |
--------------------------------------------------------------------------------
/src/components/chat/index.js:
--------------------------------------------------------------------------------
1 | import Chat from './chat-view';
2 |
3 | export default Chat;
4 |
--------------------------------------------------------------------------------
/src/components/chatroom/chatroom-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState, useContext } from 'react';
3 | import _ from 'lodash';
4 | import { AuthContext } from '../app/AuthContext';
5 |
6 | const ChatroomContainer = matchId => {
7 | const [loaded, setLoaded] = useState(false);
8 | const [loading, setLoading] = useState(false);
9 | const { authContext, socketContext } = useContext(AuthContext);
10 | const { userData, token } = authContext;
11 | const [chatroomInfo, setChatroomInfo] = useState({});
12 | const [chatroomMessages, setChatroomMessages] = useState([]);
13 | const [currentUser, setCurrentUser] = useState(null);
14 | const [message, setMessage] = useState('');
15 |
16 | socketContext.socket.on('chat message', msg => {
17 | setChatroomMessages(chatroomMessages.concat(msg));
18 | });
19 |
20 | socketContext.socket.on('redirect', msg => {
21 | window.location = `/?message=${msg}`;
22 | });
23 |
24 | const handleMessage = event => {
25 | setMessage(event.target.value);
26 | };
27 |
28 | const sendMessage = () => {
29 | if (message !== '') {
30 | socketContext.socket.emit('chat message', message, matchId);
31 | setMessage('');
32 | }
33 | };
34 |
35 | const fetchMessagesFromConversation = () => {
36 | axios
37 | .get(`${process.env.REACT_APP_PUBLIC_API_URL}/chat/${matchId}`, {
38 | headers: {
39 | 'Content-type': 'application/json; charset=UTF-8',
40 | 'x-access-token': token,
41 | },
42 | })
43 | .then(response => {
44 | if (response.data.success === false) {
45 | window.location = '/?message=access_denied';
46 | return;
47 | }
48 | setChatroomInfo({
49 | profilePicture: response.data.profilePicture,
50 | firstname: response.data.firstname,
51 | username: response.data.username,
52 | });
53 | setChatroomMessages(response.data.messages);
54 | userData.then(data => {
55 | setCurrentUser(data.data.id);
56 | socketContext.socket.emit('joinchatroom', matchId);
57 | });
58 | setLoaded(true);
59 | setLoading(false);
60 | })
61 | .catch(error => {
62 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
63 | });
64 | };
65 |
66 | if (_.isEmpty(chatroomInfo) && loaded === false && loading === false) {
67 | setLoading(true);
68 | fetchMessagesFromConversation();
69 | }
70 | return {
71 | chatroomInfo,
72 | chatroomMessages,
73 | setChatroomInfo,
74 | setChatroomMessages,
75 | loaded,
76 | currentUser,
77 | handleMessage,
78 | sendMessage,
79 | message,
80 | };
81 | };
82 |
83 | export default ChatroomContainer;
84 |
--------------------------------------------------------------------------------
/src/components/chatroom/index.js:
--------------------------------------------------------------------------------
1 | import ChatRoom from './chatroom-view';
2 |
3 | export default ChatRoom;
4 |
--------------------------------------------------------------------------------
/src/components/forgotpassword/forgotpassword-container.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { toast } from 'react-toastify';
3 | import axios from 'axios';
4 |
5 | const useForgotPasswordForm = callback => {
6 | const [inputs, setInputs] = useState({
7 | email: '',
8 | });
9 | const { email } = inputs;
10 |
11 | const sendForgotPassword = email => {
12 | axios
13 | .post(
14 | `${process.env.REACT_APP_PUBLIC_API_URL}/validation/forgotpasswordcreate`,
15 | {
16 | email,
17 | },
18 | {
19 | headers: {
20 | 'Content-type': 'application/json; charset=UTF-8',
21 | },
22 | },
23 | )
24 | .then(({ data }) => {
25 | if (data.success === true) {
26 | callback(true);
27 | } else {
28 | toast.error(data.err);
29 | }
30 | });
31 | };
32 |
33 | const handleSubmit = async event => {
34 | if (event) {
35 | event.preventDefault();
36 | await sendForgotPassword(email);
37 | }
38 | };
39 | const handleInputChange = event => {
40 | event.persist();
41 | const newInput = {
42 | ...inputs,
43 | [event.target.name]: event.target.value,
44 | };
45 | setInputs(newInput);
46 | };
47 | return {
48 | handleSubmit,
49 | handleInputChange,
50 | inputs,
51 | sendForgotPassword,
52 | };
53 | };
54 |
55 | export default useForgotPasswordForm;
56 |
--------------------------------------------------------------------------------
/src/components/forgotpassword/forgotpassword-view.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import useForgotPasswordForm from './forgotpassword-container';
3 | import Avatar from '@material-ui/core/Avatar';
4 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
5 | import TextField from '@material-ui/core/TextField';
6 | import Button from '@material-ui/core/Button';
7 | import Container from '@material-ui/core/Container';
8 | import Typography from '@material-ui/core/Typography';
9 | import Grid from '@material-ui/core/Grid';
10 | import Link from '@material-ui/core/Link';
11 | import { makeStyles } from '@material-ui/core/styles';
12 |
13 | const useStyles = makeStyles(theme => ({
14 | '@global': {
15 | body: {
16 | backgroundColor: theme.palette.common.white,
17 | },
18 | },
19 | paper: {
20 | marginTop: theme.spacing(8),
21 | display: 'flex',
22 | flexDirection: 'column',
23 | alignItems: 'center',
24 | },
25 | avatar: {
26 | margin: theme.spacing(1),
27 | backgroundColor: theme.palette.secondary.main,
28 | },
29 | form: {
30 | width: '100%',
31 | marginTop: theme.spacing(1),
32 | },
33 | submit: {
34 | margin: theme.spacing(3, 0, 2),
35 | },
36 | }));
37 |
38 | const ForgotPassword = () => {
39 | const forgotPassword = success => {
40 | if (success) {
41 | window.location = '/?message=forgotPassword_success';
42 | }
43 | };
44 | const { inputs, handleInputChange, handleSubmit } = useForgotPasswordForm(
45 | forgotPassword,
46 | );
47 | const classes = useStyles();
48 |
49 | return (
50 |
51 |
52 |
53 |
54 |
55 |
56 | Forgot your password?
57 |
58 |
95 |
96 |
97 | );
98 | };
99 |
100 | export default ForgotPassword;
101 |
--------------------------------------------------------------------------------
/src/components/forgotpassword/index.js:
--------------------------------------------------------------------------------
1 | import ForgotPassword from './forgotpassword-view';
2 |
3 | export default ForgotPassword;
4 |
--------------------------------------------------------------------------------
/src/components/home/home-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import _ from 'lodash';
3 | import { useState } from 'react';
4 |
5 | const HomeContainer = (userData, token) => {
6 | const [loaded, setLoaded] = useState(false);
7 | const [userInfo, setUserInfo] = useState({});
8 |
9 | const fetchProfile = () => {
10 | axios
11 | .get(`${process.env.REACT_APP_PUBLIC_API_URL}/users/profile`, {
12 | headers: {
13 | 'Content-type': 'application/json; charset=UTF-8',
14 | 'x-access-token': token,
15 | },
16 | })
17 | .then(response => {
18 | setUserInfo(response.data);
19 | setLoaded(true);
20 | return response.data;
21 | })
22 | .catch(error => {
23 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
24 | });
25 | };
26 |
27 | if (_.isEmpty(userInfo) && loaded === false) {
28 | fetchProfile();
29 | }
30 |
31 | return { userInfo, loaded };
32 | };
33 |
34 | export default HomeContainer;
35 |
--------------------------------------------------------------------------------
/src/components/home/index.js:
--------------------------------------------------------------------------------
1 | import Home from './home-view';
2 |
3 | export default Home;
4 |
--------------------------------------------------------------------------------
/src/components/like/index.js:
--------------------------------------------------------------------------------
1 | import Like from './like-view';
2 |
3 | export default Like;
4 |
--------------------------------------------------------------------------------
/src/components/like/like-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState, useContext } from 'react';
3 | import _ from 'lodash';
4 | import { toast } from 'react-toastify';
5 | import { AuthContext } from '../app/AuthContext';
6 |
7 | const LikeContainer = () => {
8 | const [loaded, setLoaded] = useState(false);
9 | const [likedProfile, setLikedProfile] = useState(null);
10 | const [likerProfile, setLikerProfile] = useState({});
11 | const { authContext } = useContext(AuthContext);
12 | const { userData, token } = authContext;
13 |
14 | const handleLike = likedId => {
15 | axios
16 | .get(
17 | `${process.env.REACT_APP_PUBLIC_API_URL}/likes/like-unlike/${likedId}`,
18 | {
19 | headers: {
20 | 'Content-type': 'application/json; charset=UTF-8',
21 | 'x-access-token': token,
22 | },
23 | },
24 | )
25 | .then(result => {
26 | if (result.data.blocked) {
27 | toast.error(result.data.message);
28 | } else {
29 | const indexToModify = _.keys(
30 | _.pickBy(likedProfile, { visitor: likedId }),
31 | );
32 |
33 | const newLikeedProfile = likedProfile;
34 | indexToModify.forEach(index => {
35 | newLikeedProfile[parseInt(index, 10)] = {
36 | ...newLikeedProfile[parseInt(index, 10)],
37 | liking: !likedProfile[parseInt(index, 10)].liking,
38 | };
39 | });
40 | document
41 | .querySelectorAll(`[visitor*="${likedId}"]`)
42 | .forEach(element => {
43 | if (element.classList.contains('MuiIconButton-colorSecondary'))
44 | element.classList.remove('MuiIconButton-colorSecondary');
45 | else element.className += ' MuiIconButton-colorSecondary';
46 | });
47 | setLikedProfile(newLikeedProfile);
48 | }
49 | });
50 | };
51 |
52 | const fetchLikeHistory = () =>
53 | axios
54 | .get(`${process.env.REACT_APP_PUBLIC_API_URL}/likes/`, {
55 | headers: {
56 | 'Content-type': 'application/json; charset=UTF-8',
57 | 'x-access-token': token,
58 | },
59 | })
60 | .then(response => {
61 | return response.data;
62 | })
63 | .catch(error => {
64 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
65 | });
66 |
67 | if (likedProfile === null && loaded === false) {
68 | Promise.all([userData, fetchLikeHistory()]).then(values => {
69 | setLikedProfile(values[1]);
70 | setLikerProfile(values[0].data);
71 | setLoaded(true);
72 | });
73 | }
74 |
75 | return { likedProfile, likerProfile, loaded, handleLike };
76 | };
77 |
78 | export default LikeContainer;
79 |
--------------------------------------------------------------------------------
/src/components/like/like-view.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CircularProgress from '@material-ui/core/CircularProgress';
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import LikeContainer from './like-container';
5 | import Title from '../shared/title';
6 | import ProfilesGrid from '../shared/profiles-grid';
7 |
8 | const useStyles = makeStyles(theme => ({
9 | wrapper: {
10 | display: 'flex',
11 | flexDirection: 'row',
12 | justifyContent: 'center',
13 | marginLeft: 'auto',
14 | marginRight: 'auto',
15 | maxWidth: '1500px',
16 | marginTop: '10px',
17 | },
18 | center: {
19 | display: 'flex',
20 | flexDirection: 'column',
21 | justifyContent: 'center',
22 | alignItems: 'center',
23 | },
24 | progress: {
25 | height: '100vh',
26 | display: 'flex',
27 | flexDirection: 'row',
28 | justifyContent: 'center',
29 | alignItems: 'center',
30 | },
31 | emptyPageWrapper: {
32 | display: 'flex',
33 | flexDirection: 'row',
34 | justifyContent: 'center',
35 | },
36 | emptyPageText: {
37 | maxWidth: "700px",
38 | margin: theme.spacing(2),
39 | },
40 | }));
41 |
42 | const Like = ({ computedMatch }) => {
43 | const likedUsername = computedMatch.params.username;
44 |
45 | const { likedProfile, likerProfile, loaded, handleLike } = LikeContainer(
46 | likedUsername,
47 | );
48 | const classes = useStyles();
49 |
50 | if (loaded === false) {
51 | return (
52 |
53 |
54 |
55 | );
56 | }
57 |
58 | return (
59 | <>
60 |
61 |
68 | >
69 | );
70 | };
71 |
72 | export default Like;
73 |
--------------------------------------------------------------------------------
/src/components/login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './login-view'
2 |
3 | export default Login
--------------------------------------------------------------------------------
/src/components/login/login-container.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import axios from 'axios';
3 | import { toast } from 'react-toastify';
4 |
5 | const useLoginForm = callback => {
6 | const [inputs, setInputs] = useState({
7 | username: '',
8 | password: '',
9 | });
10 | const { username, password } = inputs;
11 |
12 | const ipLocation = () => {
13 | return new Promise(resolve => {
14 | const locationApi = axios.get('https://ipapi.co/json').then(response => {
15 | return [response.data.latitude, response.data.longitude];
16 | });
17 | resolve(locationApi);
18 | });
19 | };
20 | const geoLocation = () => {
21 | return new Promise(resolve => {
22 | navigator.geolocation.getCurrentPosition(position => {
23 | const currentLocation = [0.0, 0.0];
24 | currentLocation[0] = position.coords.latitude;
25 | currentLocation[1] = position.coords.longitude;
26 | resolve(currentLocation);
27 | });
28 | setTimeout(resolve, 2000);
29 | });
30 | };
31 |
32 | const userLocation = async () => {
33 | const ip = ipLocation();
34 | const geo = await geoLocation();
35 | return geo || ip;
36 | };
37 |
38 | const handleSubmit = async event => {
39 | if (event) {
40 | event.preventDefault();
41 | const location = await userLocation();
42 | axios
43 | .post(
44 | `${process.env.REACT_APP_PUBLIC_API_URL}/auth/login`,
45 | {
46 | username,
47 | password,
48 | lat: location[1],
49 | lon: location[0],
50 | },
51 | {
52 | headers: {
53 | 'Content-type': 'application/json; charset=UTF-8',
54 | },
55 | },
56 | )
57 | .then(({ data }) => {
58 | if (data.success === true) {
59 | localStorage.setItem('token', data.token);
60 | callback(true);
61 | } else {
62 | toast.error(data.err);
63 | }
64 | });
65 | }
66 | };
67 |
68 | const handleInputChange = event => {
69 | event.persist();
70 | const newInput = {
71 | ...inputs,
72 | [event.target.name]: event.target.value,
73 | };
74 | setInputs(newInput);
75 | };
76 | return {
77 | handleSubmit,
78 | handleInputChange,
79 | inputs,
80 | };
81 | };
82 |
83 | export default useLoginForm;
84 |
--------------------------------------------------------------------------------
/src/components/login/login-view.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Avatar from '@material-ui/core/Avatar';
3 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
4 | import TextField from '@material-ui/core/TextField';
5 | import Button from '@material-ui/core/Button';
6 | import Container from '@material-ui/core/Container';
7 | import Typography from '@material-ui/core/Typography';
8 | import Grid from '@material-ui/core/Grid';
9 | import Link from '@material-ui/core/Link';
10 | import { makeStyles } from '@material-ui/core/styles';
11 | import useLoginForm from './login-container';
12 |
13 | const useStyles = makeStyles(theme => ({
14 | '@global': {
15 | body: {
16 | backgroundColor: theme.palette.common.white,
17 | },
18 | },
19 | paper: {
20 | marginTop: theme.spacing(8),
21 | display: 'flex',
22 | flexDirection: 'column',
23 | alignItems: 'center',
24 | },
25 | avatar: {
26 | margin: theme.spacing(1),
27 | backgroundColor: theme.palette.secondary.main,
28 | },
29 | form: {
30 | width: '100%',
31 | marginTop: theme.spacing(1),
32 | },
33 | submit: {
34 | margin: theme.spacing(3, 0, 2),
35 | },
36 | }));
37 |
38 | const Login = () => {
39 | const login = success => {
40 | if (success) {
41 | window.location = '/?message=login_success';
42 | }
43 | };
44 | const { inputs, handleInputChange, handleSubmit } = useLoginForm(login);
45 | const classes = useStyles();
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
53 |
54 | Sign in
55 |
56 |
107 |
108 |
109 | );
110 | };
111 |
112 | export default Login;
113 |
--------------------------------------------------------------------------------
/src/components/nav/components/notificationDrawer.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 | import moment from 'moment';
3 | import {
4 | List,
5 | ListItem,
6 | Divider,
7 | ListItemText,
8 | ListItemAvatar,
9 | Avatar,
10 | } from '@material-ui/core';
11 | import Typography from '@material-ui/core/Typography';
12 | import _ from 'lodash';
13 |
14 | const NotificationDrawer = ({ classes, toggleDrawer, notifications }) => {
15 | const messageToDisplay = (sender, event) => {
16 | let message = '';
17 |
18 | if (event) {
19 | switch (event) {
20 | case 'message':
21 | message = `${sender} sent you a new message`;
22 | break;
23 | case 'like':
24 | message = `${sender} liked you`;
25 | break;
26 | case 'visit':
27 | message = `${sender} visited your profile!`;
28 | break;
29 | case 'match':
30 | message = `${sender} liked you back, it's a match!`;
31 | break;
32 | case 'unmatch':
33 | message = `${sender} unliked you, no more match. Sorry!`;
34 | break;
35 | default:
36 | }
37 | return message;
38 | }
39 | };
40 |
41 | return (
42 |
48 | {_.isEmpty(notifications) ? (
49 |
50 |
51 | You don't have any notifications yet! Be patient, it's coming 😌
52 |
53 |
54 | ) : (
55 |
56 | {notifications.map(notification => {
57 | const notifLink = `/profile/${notification.username}`;
58 | return (
59 |
60 |
66 |
67 |
68 |
69 |
76 |
77 |
78 |
79 | );
80 | })}
81 |
82 | )}
83 |
84 | );
85 | };
86 |
87 | export default NotificationDrawer;
88 |
--------------------------------------------------------------------------------
/src/components/nav/index.js:
--------------------------------------------------------------------------------
1 | import Nav from './nav-view';
2 |
3 | export default Nav;
4 |
--------------------------------------------------------------------------------
/src/components/nav/nav-container.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/elitedev201/React-Dating-app/11b2954e5de0d2504c41abaafbf12b417e1a6f32/src/components/nav/nav-container.js
--------------------------------------------------------------------------------
/src/components/profile/components/cropper/cropImage.js:
--------------------------------------------------------------------------------
1 | const createImage = url =>
2 | new Promise((resolve, reject) => {
3 | const image = new Image();
4 | image.addEventListener('load', () => resolve(image));
5 | image.addEventListener('error', error => reject(error));
6 | image.setAttribute('crossOrigin', 'anonymous'); // needed to avoid cross-origin issues on CodeSandbox
7 | image.src = url;
8 | });
9 |
10 | /**
11 | * This function was adapted from the one in the ReadMe of https://github.com/DominicTobias/react-image-crop
12 | * @param {File} image - Image File url
13 | * @param {Object} pixelCrop - pixelCrop Object provided by react-easy-crop
14 | * @param {number} rotation - optional rotation parameter
15 | */
16 | export default async function getCroppedImg(imageSrc, pixelCrop, upload) {
17 | const image = await createImage(imageSrc);
18 | const canvas = document.createElement('canvas');
19 | const ctx = canvas.getContext('2d');
20 |
21 | const safeArea = Math.max(image.width, image.height);
22 |
23 | // set each dimensions to double largest dimension to allow for a safe area for the
24 | // image to rotate in without being clipped by canvas context
25 | const ios =
26 | !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
27 | canvas.width = ios ? 4096 : safeArea;
28 | canvas.height = ios ? 4096 : safeArea;
29 |
30 | // draw rotated image and store data.
31 | ctx.drawImage(image, safeArea - image.width, safeArea - image.height);
32 | const data = ctx.getImageData(0, 0, safeArea, safeArea);
33 |
34 | // set canvas width to final desired crop size - this will clear existing context
35 | canvas.width = pixelCrop.width;
36 | canvas.height = pixelCrop.height;
37 |
38 | // paste generated rotate image with correct offsets for x,y crop values.
39 | ctx.putImageData(
40 | data,
41 | 0 - safeArea + image.width - pixelCrop.x,
42 | 0 - safeArea + image.height - pixelCrop.y,
43 | );
44 |
45 | // As Base64 string
46 | // return canvas.toDataURL('image/jpeg');
47 |
48 | // As a blob
49 | return new Promise(resolve => {
50 | canvas.toBlob(file => {
51 | upload(file);
52 | resolve(URL.createObjectURL(file));
53 | }, 'image/jpeg');
54 | });
55 | }
56 |
--------------------------------------------------------------------------------
/src/components/profile/components/cropper/cropper.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useCallback } from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import Slider from '@material-ui/core/Slider';
4 | import Cropper from 'react-easy-crop';
5 | import Button from '@material-ui/core/Button';
6 | import ArrowBackIcon from '@material-ui/icons/ArrowBack';
7 | import getCroppedImg from './cropImage';
8 |
9 | const useCropperStyles = makeStyles(() => ({
10 | cropWrapper: {
11 | position: 'relative',
12 | height: '60vh',
13 | },
14 | cropContainer: {
15 | position: 'absolute',
16 | top: '0',
17 | left: '0',
18 | right: '0',
19 | bottom: '80px',
20 | backgroundColor: 'white',
21 | },
22 | controls: {
23 | position: 'absolute',
24 | bottom: '0',
25 | left: '50%',
26 | width: '50%',
27 | transform: 'translateX(-50%)',
28 | height: '80px',
29 | display: 'flex',
30 | alignItems: 'center',
31 | },
32 | slider: {
33 | padding: '22px 0px',
34 | },
35 | cropButton: {
36 | flexShrink: '0',
37 | marginLeft: '16',
38 | },
39 | }));
40 |
41 | const CropperImg = ({
42 | imageToSave,
43 | croppedImage,
44 | setCroppedImage,
45 | upload,
46 | finalImage,
47 | sendCroppedImageServer,
48 | }) => {
49 | const classes = useCropperStyles();
50 | const [crop, setCrop] = useState({ x: 0, y: 0 });
51 | const [zoom, setZoom] = useState(1);
52 | const [croppedAreaPixels, setCroppedAreaPixels] = useState(null);
53 | const onCropComplete = useCallback((croppedArea, croppedAreaPixels) => {
54 | setCroppedAreaPixels(croppedAreaPixels);
55 | }, []);
56 |
57 | const showCroppedImage = useCallback(async () => {
58 | try {
59 | getCroppedImg(imageToSave, croppedAreaPixels, upload).then(image =>
60 | setCroppedImage(image),
61 | );
62 | } catch (e) {
63 | console.error(e);
64 | }
65 | }, [setCroppedImage, imageToSave, croppedAreaPixels, upload]);
66 |
67 | const closeCroppedImg = useCallback(() => {
68 | setCroppedImage(null);
69 | }, [setCroppedImage]);
70 |
71 | if (!croppedImage) {
72 | return (
73 |
74 |
75 |
84 |
85 |
86 | setZoom(zoom)}
93 | />
94 |
95 |
101 | Show Result
102 |
103 |
104 | );
105 | }
106 | return (
107 |
108 |
109 |
110 |
111 |
117 |
118 |
119 |
{
121 | closeCroppedImg();
122 | sendCroppedImageServer(finalImage);
123 | }}
124 | variant="contained"
125 | color="secondary"
126 | classes={{ root: classes.cropButton }}
127 | >
128 | Save image
129 |
130 |
131 | );
132 | };
133 |
134 | export default CropperImg;
135 |
--------------------------------------------------------------------------------
/src/components/profile/components/current-pictures.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import HighlightOffIcon from '@material-ui/icons/HighlightOff';
3 | import Fab from '@material-ui/core/Fab';
4 |
5 | const CurrentPictures = ({
6 | pictures,
7 | classes,
8 | Grid,
9 | Box,
10 | handleDeleteImage,
11 | handleChangeProfileImage,
12 | profilePicture,
13 | }) => {
14 | if (pictures) {
15 | return pictures.map(pictureUrl => (
16 |
24 | handleChangeProfileImage(pictureUrl)}
34 | />
35 | handleDeleteImage(pictureUrl)}
40 | >
41 |
42 |
43 |
44 | ));
45 | }
46 | return (
47 |
48 |
53 | Aucune photo
54 |
55 |
56 | );
57 | };
58 | export default CurrentPictures;
59 |
--------------------------------------------------------------------------------
/src/components/profile/components/formCheckBox.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Typography from '@material-ui/core/Typography';
3 | import Box from '@material-ui/core/Box';
4 | import FormControlLabel from '@material-ui/core/FormControlLabel';
5 | import FormControl from '@material-ui/core/FormControl';
6 | import FormGroup from '@material-ui/core/FormGroup';
7 | import Checkbox from '@material-ui/core/Checkbox';
8 |
9 | const formCheckBox = ({
10 | title,
11 | classes,
12 | isChecked,
13 | handleProfileChange,
14 | name,
15 | label,
16 | }) => {
17 | if (label) {
18 | return (
19 | <>
20 |
21 | {title}
22 |
23 |
24 |
25 | {label.map((checkbox, index) => (
26 |
35 | }
36 | label={label[index]}
37 | />
38 | ))}
39 |
40 |
41 | >
42 | );
43 | }
44 | };
45 |
46 | export default formCheckBox;
47 |
--------------------------------------------------------------------------------
/src/components/profile/components/inputTextShort.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import moment from 'moment';
3 |
4 | const InputTextShort = ({
5 | classes,
6 | Typography,
7 | Box,
8 | TextField,
9 | profile,
10 | handleProfileChange,
11 | name,
12 | value,
13 | title,
14 | type,
15 | }) => {
16 | if (type === 'date') {
17 | return (
18 |
19 |
20 | {title}
21 |
22 |
43 |
44 | );
45 | }
46 | if (name === 'description') {
47 | return (
48 |
49 |
50 | {title}
51 |
52 |
63 |
64 | );
65 | }
66 | return (
67 |
68 |
69 | {title}
70 |
71 |
80 |
81 | );
82 | };
83 |
84 | export default InputTextShort;
85 |
--------------------------------------------------------------------------------
/src/components/profile/components/location/address-autocomplete.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GooglePlacesAutocomplete, {
3 | getLatLng,
4 | geocodeByAddress,
5 | } from 'react-google-places-autocomplete';
6 | import 'react-google-places-autocomplete/dist/assets/index.css';
7 |
8 | const AddressAutocomplete = ({ handleChangeLocation, classes }) => {
9 | const changeLocation = address => {
10 | geocodeByAddress(address)
11 | .then(results => getLatLng(results[0]))
12 | .then(({ lat, lng }) => {
13 | const newLocation = [lat, lng];
14 | handleChangeLocation(newLocation);
15 | });
16 | };
17 | return (
18 | <>
19 | {
22 | changeLocation(result.description);
23 | }}
24 | />
25 | >
26 | );
27 | };
28 |
29 | export default AddressAutocomplete;
30 |
--------------------------------------------------------------------------------
/src/components/profile/components/location/cityGuess.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | const CityGuess = ({ lat, lon, profile, handleChangeCity }) => {
4 | if (!profile.city)
5 | axios
6 | .get(
7 | `https://eu1.locationiq.com/v1/reverse.php?key=9ade280ec73aeb&lat=${lat}&lon=${lon}&format=json`,
8 | )
9 | .then(data => {
10 | handleChangeCity(data.data.address.city);
11 | });
12 | return ` | ${profile.city}` || 'Location undefined';
13 | };
14 |
15 | export default CityGuess;
16 |
--------------------------------------------------------------------------------
/src/components/profile/components/location/map.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import GoogleMapReact from 'google-map-react';
3 |
4 | const Marker = () => (
5 |
11 | );
12 |
13 | const Map = ({ lat, lon }) => {
14 | const [center] = useState({ lat, lng: lon });
15 | const [zoom] = useState(11);
16 | return (
17 |
18 |
23 |
24 |
25 |
26 | );
27 | };
28 |
29 | export default Map;
30 |
--------------------------------------------------------------------------------
/src/components/profile/components/modal.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from 'react-bootstrap/Modal';
3 | import CropperImg from './cropper/cropper';
4 | import 'bootstrap/dist/css/bootstrap.min.css';
5 |
6 | const ModalCrop = ({
7 | showModal,
8 | setShowModal,
9 | imageToSave,
10 | croppedImage,
11 | setCroppedImage,
12 | upload,
13 | finalImage,
14 | sendCroppedImageServer,
15 | }) => {
16 | return (
17 | <>
18 | {
21 | setShowModal(false);
22 | setCroppedImage(null);
23 | }}
24 | dialogClassName="modal-90w"
25 | aria-labelledby="example-custom-modal-styling-title"
26 | >
27 |
28 |
29 | Crop your image
30 |
31 |
32 |
33 |
41 |
42 |
43 | >
44 | );
45 | };
46 |
47 | export default ModalCrop;
48 |
--------------------------------------------------------------------------------
/src/components/profile/components/tabPanelProfileAbout.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Grid from '@material-ui/core/Grid';
3 | import Typography from '@material-ui/core/Typography';
4 | import Box from '@material-ui/core/Box';
5 | import Button from '@material-ui/core/Button';
6 | import TextField from '@material-ui/core/TextField';
7 | import AddAPhotoIcon from '@material-ui/icons/AddAPhoto';
8 | import CurrentPictures from './current-pictures';
9 | import FormCheckBox from './formCheckBox';
10 | import InputTextShort from './inputTextShort';
11 |
12 | function TabPanel(props) {
13 | const { children, value, index } = props;
14 |
15 | return (
16 |
23 | {children}
24 |
25 | );
26 | }
27 |
28 | const TabPanelProfileAbout = ({
29 | value,
30 | index,
31 | classes,
32 | profile,
33 | isChecked,
34 | handleProfileChange,
35 | handleSubmitParameters,
36 | handleFileUpload,
37 | handleChangeProfileImage,
38 | handleDeleteImage,
39 | }) => {
40 | return (
41 |
42 |
43 |
44 |
60 |
76 |
88 |
89 |
95 | Save changes
96 |
97 |
98 |
99 |
100 |
101 | My pictures
102 |
103 |
104 |
105 |
115 | {profile.images && profile.images.length < 5 ? (
116 |
117 | }
123 | >
124 | Upload a picture
125 |
126 |
133 |
134 | ) : null}
135 |
136 |
137 |
138 |
139 |
140 | );
141 | };
142 |
143 | export default TabPanelProfileAbout;
144 |
--------------------------------------------------------------------------------
/src/components/profile/index.js:
--------------------------------------------------------------------------------
1 | import Profile from './profile-view';
2 |
3 | export default Profile;
4 |
--------------------------------------------------------------------------------
/src/components/profileshow/components/chipsList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Chip from '@material-ui/core/Chip';
3 | import Box from '@material-ui/core/Box';
4 |
5 | const findEquivalence = number => {
6 | let label;
7 |
8 | switch (number) {
9 | case 1:
10 | label = 'Woman';
11 | break;
12 | case 2:
13 | label = 'Man';
14 | break;
15 | case 3:
16 | label = 'Cis Woman';
17 | break;
18 | case 4:
19 | label = 'Cis Man';
20 | break;
21 | case 5:
22 | label = 'Trans Woman';
23 | break;
24 | case 6:
25 | label = 'Trans Man';
26 | break;
27 | case 7:
28 | label = 'Non-binary';
29 | break;
30 | default:
31 | }
32 | return label;
33 | };
34 |
35 | const ChipsList = ({ classes, list, type }) => {
36 | if (list) {
37 | if (type === 'gender' || type === 'preference') {
38 | return (
39 |
40 | {list.map(item => (
41 |
42 | ))}
43 |
44 | );
45 | }
46 | return (
47 |
48 | {list.map(item => (
49 |
50 | ))}
51 |
52 | );
53 | }
54 | };
55 |
56 | export default ChipsList;
57 |
--------------------------------------------------------------------------------
/src/components/profileshow/components/loggedDot.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import moment from 'moment';
4 |
5 | const useStyles = makeStyles(theme => ({
6 | loggedDot: {
7 | marginTop: '10px',
8 | marginBottom: '3px',
9 | width: '10px',
10 | height: '10px',
11 | borderRadius: '50%',
12 | backgroundColor: '#64dd17',
13 | },
14 | notLoggedDot: {
15 | marginTop: '10px',
16 | marginBottom: '3px',
17 | width: '10px',
18 | height: '10px',
19 | borderRadius: '50%',
20 | backgroundColor: '#b0bec5',
21 | },
22 | dotDateWrapper: {
23 | display: 'flex',
24 | flexDirection: 'row',
25 | alignItems: 'flex-end',
26 | },
27 | lastConnectionDate: {
28 | marginLeft: theme.spacing(1),
29 | fontSize: '0.7em',
30 | },
31 | }));
32 |
33 | const LoggedDot = ({ loggedState, lastConnection, displayLast }) => {
34 | const classes = useStyles();
35 | if (loggedState === true) {
36 | return (
37 |
38 |
39 |
40 | );
41 | }
42 | return (
43 |
44 |
45 | {displayLast ? (
46 |
47 | Disconnected {moment(lastConnection).fromNow()}
48 |
49 | ) : null}
50 |
51 | );
52 | };
53 |
54 | export default LoggedDot;
55 |
--------------------------------------------------------------------------------
/src/components/profileshow/index.js:
--------------------------------------------------------------------------------
1 | import ProfileShow from './profileshow-view';
2 |
3 | export default ProfileShow;
4 |
--------------------------------------------------------------------------------
/src/components/profileshow/profileshow-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState, useContext } from 'react';
3 | import _ from 'lodash';
4 | import { toast } from 'react-toastify';
5 | import { AuthContext } from '../app/AuthContext';
6 |
7 | const ProfileShowContainer = visitedUsername => {
8 | const [visitedProfile, setVisitedProfile] = useState({});
9 | const [loaded, setLoaded] = useState(false);
10 | const [loading, setLoading] = useState(false);
11 | const { authContext } = useContext(AuthContext);
12 | const { token } = authContext;
13 |
14 | const handleBlock = (blockedId, blocked, setBlocked) => {
15 | axios
16 | .post(
17 | `${process.env.REACT_APP_PUBLIC_API_URL}/block/block-unblock/${blockedId}`,
18 | {},
19 | {
20 | headers: {
21 | 'Content-type': 'application/json; charset=UTF-8',
22 | 'x-access-token': token,
23 | },
24 | },
25 | )
26 | .then(result => {
27 | if (result.data.deleted === true) {
28 | setBlocked(false);
29 | toast.success('You just unblocked this user');
30 | }
31 | if (result.data.created === true) {
32 | setBlocked(true);
33 | toast.success('You just blocked this user');
34 | }
35 | })
36 | .catch(error => {
37 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
38 | });
39 | };
40 |
41 | const handleReport = (reportedId, reported, setReported) => {
42 | axios
43 | .post(
44 | `${process.env.REACT_APP_PUBLIC_API_URL}/report/${reportedId}`,
45 | {},
46 | {
47 | headers: {
48 | 'Content-type': 'application/json; charset=UTF-8',
49 | 'x-access-token': token,
50 | },
51 | },
52 | )
53 | .then(result => {
54 | if (result.data.success) {
55 | setReported(true);
56 | }
57 | })
58 | .catch(error => {
59 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
60 | });
61 | };
62 |
63 | const handleLike = (likedId, setLiked) => {
64 | axios
65 | .get(
66 | `${process.env.REACT_APP_PUBLIC_API_URL}/likes/like-unlike/${likedId}`,
67 | {
68 | headers: {
69 | 'Content-type': 'application/json; charset=UTF-8',
70 | 'x-access-token': token,
71 | },
72 | },
73 | )
74 | .then(result => {
75 | if (result.data.deleted === true) {
76 | setLiked(false);
77 | toast.success('You just unliked this user');
78 | if (visitedProfile.match === true) {
79 | setVisitedProfile({ ...visitedProfile, match: false });
80 | }
81 | }
82 | if (result.data.created === true) {
83 | setLiked(true);
84 | toast.success('You just liked this user');
85 | if (
86 | visitedProfile.match === false &&
87 | visitedProfile.visitedlikevisitor === true
88 | ) {
89 | toast.info("It's a match!");
90 | setVisitedProfile({ ...visitedProfile, match: true });
91 | }
92 | }
93 | })
94 | .catch(error => {
95 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
96 | });
97 | };
98 |
99 | const fetchVisitedProfile = () =>
100 | axios
101 | .get(
102 | `${process.env.REACT_APP_PUBLIC_API_URL}/users/profile/${visitedUsername}`,
103 | {
104 | headers: {
105 | 'Content-type': 'application/json; charset=UTF-8',
106 | 'x-access-token': token,
107 | },
108 | },
109 | )
110 | .then(response => {
111 | return response.data;
112 | })
113 | .catch(error => {
114 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
115 | });
116 |
117 | if (_.isEmpty(visitedProfile) && loading === false) {
118 | setLoading(true);
119 | fetchVisitedProfile().then(data => {
120 | if (data.founded === true) {
121 | setVisitedProfile(data);
122 | setLoaded(true);
123 | setLoading(false);
124 | } else if (data.success === false) {
125 | window.location = '/?message=user_not_found';
126 | } else if (data.authorized === false) {
127 | window.location = '/profile?message=profile_not_completed';
128 | } else if (data.blocked === true) {
129 | window.location = '/?message=user_blocked_you';
130 | }
131 | });
132 | }
133 |
134 | return { visitedProfile, loaded, handleBlock, handleReport, handleLike };
135 | };
136 |
137 | export default ProfileShowContainer;
138 |
--------------------------------------------------------------------------------
/src/components/search/index.js:
--------------------------------------------------------------------------------
1 | import Search from './search-view';
2 |
3 | export default Search;
4 |
--------------------------------------------------------------------------------
/src/components/search/search-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState, useContext } from 'react';
3 | import { toast } from 'react-toastify';
4 | import _ from 'lodash';
5 | import { AuthContext } from '../app/AuthContext';
6 |
7 | const SearchContainer = () => {
8 | const [loaded, setLoaded] = useState(false);
9 | const [currentUserProfile, setCurrentUserProfile] = useState({});
10 | const [searchResult, setSearchResult] = useState([]);
11 | const [searchOptions, setSearchOptions] = useState({
12 | ageRange: [18, 85],
13 | popularityRange: [0, 100],
14 | interests: [],
15 | distanceMax: 100,
16 | sort: '',
17 | });
18 | const { authContext } = useContext(AuthContext);
19 | const { userData, token } = authContext;
20 |
21 | const handleLike = likedId => {
22 | axios
23 | .get(
24 | `${process.env.REACT_APP_PUBLIC_API_URL}/likes/like-unlike/${likedId}`,
25 | {
26 | headers: {
27 | 'Content-type': 'application/json; charset=UTF-8',
28 | 'x-access-token': token,
29 | },
30 | },
31 | )
32 | .then(result => {
33 | if (result.data.blocked) {
34 | toast.error(result.data.message);
35 | } else {
36 | const indexToModify = _.keys(
37 | _.pickBy(searchResult, { visitor: likedId }),
38 | );
39 |
40 | const newSearchResult = searchResult;
41 | indexToModify.forEach(index => {
42 | newSearchResult[parseInt(index, 10)] = {
43 | ...newSearchResult[parseInt(index, 10)],
44 | liking: !searchResult[parseInt(index, 10)].liking,
45 | };
46 | });
47 | document
48 | .querySelectorAll(`[visitor*="${likedId}"]`)
49 | .forEach(element => {
50 | if (element.classList.contains('MuiIconButton-colorSecondary'))
51 | element.classList.remove('MuiIconButton-colorSecondary');
52 | else element.className += ' MuiIconButton-colorSecondary';
53 | });
54 | setSearchResult(newSearchResult);
55 | }
56 | });
57 | };
58 |
59 | const handleSort = (event, profiles = searchResult) => {
60 | let sortChoice = '';
61 | if (event) {
62 | sortChoice = event.target.value;
63 | } else {
64 | sortChoice = searchOptions.sort;
65 | }
66 | let order = '';
67 | switch (sortChoice) {
68 | case 'distance':
69 | order = 'asc';
70 | break;
71 | case 'ageAsc':
72 | order = 'asc';
73 | break;
74 | case 'ageDesc':
75 | order = 'desc';
76 | break;
77 | case 'popularity':
78 | order = 'desc';
79 | break;
80 | case 'interests':
81 | order = 'asc';
82 | break;
83 | default:
84 | }
85 | const newSearchOptions = { ...searchOptions, sort: sortChoice };
86 | setSearchOptions(newSearchOptions);
87 | setSearchResult(
88 | _.orderBy(
89 | profiles,
90 | [
91 | profile => {
92 | switch (sortChoice) {
93 | case 'distance':
94 | return profile.distance;
95 | case 'ageAsc':
96 | return profile.age;
97 | case 'ageDesc':
98 | return profile.age;
99 | case 'popularity':
100 | return profile.popularityRate;
101 | case 'interests':
102 | return profile.interests[0] ? profile.interests[0] : 'ZZZZ';
103 | default:
104 | }
105 | },
106 | ],
107 | [order],
108 | ),
109 | );
110 | };
111 |
112 | const handleChangeSlider = (type, newValue) => {
113 | if (type === 'interests') {
114 | newValue = newValue.map(interest => {
115 | return interest.name;
116 | });
117 | const newSearchOptions = { ...searchOptions, [type]: newValue };
118 | setSearchOptions(newSearchOptions);
119 | fetchSearch(newSearchOptions);
120 | return;
121 | }
122 | const newSearchOptions = { ...searchOptions, [type]: newValue };
123 | setSearchOptions(newSearchOptions);
124 | };
125 |
126 | const fetchSearch = (searchQuery = searchOptions) => {
127 | axios
128 | .post(
129 | `${process.env.REACT_APP_PUBLIC_API_URL}/users/search`,
130 | searchQuery,
131 | {
132 | headers: {
133 | 'Content-type': 'application/json; charset=UTF-8',
134 | 'x-access-token': token,
135 | },
136 | },
137 | )
138 | .then(result => {
139 | if (result.data.authorized === false) {
140 | window.location = '/profile?message=profile_not_completed';
141 | return;
142 | }
143 | handleSort(null, result.data);
144 | });
145 | };
146 |
147 | if (_.isEmpty(currentUserProfile) && loaded === false) {
148 | userData.then(value => {
149 | setCurrentUserProfile(value.data);
150 | fetchSearch();
151 | setLoaded(true);
152 | });
153 | }
154 | return {
155 | currentUserProfile,
156 | loaded,
157 | searchResult,
158 | searchOptions,
159 | handleChangeSlider,
160 | setSearchOptions,
161 | fetchSearch,
162 | handleLike,
163 | handleSort,
164 | };
165 | };
166 |
167 | export default SearchContainer;
168 |
--------------------------------------------------------------------------------
/src/components/search/search-view.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CircularProgress from '@material-ui/core/CircularProgress';
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import Divider from '@material-ui/core/Divider';
5 | import SearchFilters from './components/search-filters';
6 | import ProfilesGrid from '../shared/profiles-grid';
7 | import Title from '../shared/title';
8 | import SearchContainer from './search-container';
9 |
10 | const useStyles = makeStyles(theme => ({
11 | wrapper: {
12 | display: 'flex',
13 | flexDirection: 'row',
14 | justifyContent: 'center',
15 | marginLeft: 'auto',
16 | marginRight: 'auto',
17 | maxWidth: '1500px',
18 | marginTop: '10px',
19 | },
20 | center: {
21 | display: 'flex',
22 | flexDirection: 'column',
23 | justifyContent: 'center',
24 | alignItems: 'center',
25 | },
26 | progress: {
27 | height: '100vh',
28 | display: 'flex',
29 | flexDirection: 'row',
30 | justifyContent: 'center',
31 | alignItems: 'center',
32 | },
33 | filtersContainer: {
34 | width: '100%',
35 | display: 'flex',
36 | flexDirection: 'column',
37 | justifyContent: 'space-between',
38 | padding: '10px',
39 | },
40 | filtersButtonContainer: {
41 | width: '100%',
42 | display: 'flex',
43 | flexDirection: 'row',
44 | justifyContent: 'space-around',
45 | position: 'relative',
46 | },
47 | sliderContainer: {
48 | position: 'absolute',
49 | backgroundColor: 'red',
50 | },
51 | slider: {
52 | width: '100%',
53 | },
54 | menuItem: {
55 | width: '600px',
56 | height: '300px',
57 | textDecoration: 'none',
58 | },
59 | titleGutterbottom: {
60 | marginBottom: theme.spacing(2),
61 | },
62 | }));
63 |
64 | const Search = () => {
65 | const {
66 | currentUserProfile,
67 | loaded,
68 | searchResult,
69 | searchOptions,
70 | handleChangeSlider,
71 | fetchSearch,
72 | handleLike,
73 | handleSort,
74 | } = SearchContainer();
75 | const classes = useStyles();
76 |
77 | if (loaded === false) {
78 | return (
79 |
80 |
81 |
82 | );
83 | }
84 |
85 | return (
86 | <>
87 |
88 |
96 |
97 |
104 | >
105 | );
106 | };
107 |
108 | export default Search;
109 |
--------------------------------------------------------------------------------
/src/components/shared/components/media-card.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import Card from '@material-ui/core/Card';
4 | import CardActionArea from '@material-ui/core/CardActionArea';
5 | import CardContent from '@material-ui/core/CardContent';
6 | import CardMedia from '@material-ui/core/CardMedia';
7 | import Typography from '@material-ui/core/Typography';
8 | import FavoriteIcon from '@material-ui/icons/Favorite';
9 | import IconButton from '@material-ui/core/IconButton';
10 | import Avatar from '@material-ui/core/Avatar';
11 | import Box from '@material-ui/core/Box';
12 | import Tooltip from '@material-ui/core/Tooltip';
13 | import moment from 'moment';
14 | import { getDistance } from 'geolib';
15 | import LoggedDot from '../../profileshow/components/loggedDot';
16 |
17 | const useStyles = makeStyles(theme => ({
18 | card: {
19 | maxWidth: 345,
20 | width: '100%',
21 | },
22 | media: {
23 | height: 345,
24 | position: 'relative',
25 | },
26 | cardContentMatch: {
27 | backgroundImage: `url("https://media.giphy.com/media/26ufcYAkp8e66vanu/giphy.gif")`,
28 | },
29 | avatar: {
30 | margin: theme.spacing(1),
31 | backgroundColor: theme.palette.secondary.main,
32 | fontSize: '0.8em',
33 | padding: theme.spacing(1),
34 | },
35 | matchingRate: {
36 | backgroundColor: theme.palette.primary.main,
37 | borderRadius: '50px',
38 | color: 'white',
39 | paddingTop: theme.spacing(1),
40 | paddingBottom: theme.spacing(1),
41 | paddingLeft: theme.spacing(2),
42 | paddingRight: theme.spacing(2),
43 | display: 'flex',
44 | flexDirection: 'column',
45 | alignItems: 'center',
46 | position: 'absolute',
47 | top: '20px',
48 | right: '20px',
49 | boxShadow: '2px 2px 6px 0px black',
50 | },
51 | titleGutterbottom: {
52 | marginBottom: theme.spacing(2),
53 | },
54 | }));
55 |
56 | export default function MediaCard({ field, profile, handleLike, type }) {
57 | const classes = useStyles();
58 |
59 | const {
60 | firstname,
61 | username,
62 | birthDate,
63 | location,
64 | popularityRate,
65 | profilePicture,
66 | date,
67 | liking,
68 | liked,
69 | visitor,
70 | lastConnection,
71 | connected,
72 | match,
73 | score,
74 | } = field;
75 |
76 | const getAge = dateString => {
77 | const today = new Date();
78 | const birthDate = new Date(dateString);
79 | let age = today.getFullYear() - birthDate.getFullYear();
80 | const m = today.getMonth() - birthDate.getMonth();
81 | if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {
82 | age -= 1;
83 | }
84 | return `${age} ans `;
85 | };
86 |
87 | const distance = () => {
88 | const { location: visitorLocation } = profile;
89 | let dist = getDistance(
90 | { latitude: location[0], longitude: location[1] },
91 | { latitude: visitorLocation[0], longitude: visitorLocation[1] },
92 | );
93 | dist = Math.round(dist / 1000);
94 | return ` | ${dist} km`;
95 | };
96 |
97 | const lastVisit = moment(date).fromNow();
98 |
99 | return (
100 |
101 | {
103 | window.location = `/profile/${username}`;
104 | }}
105 | >
106 |
114 | {type === 'suggestion' ? (
115 |
116 | Match rate
117 | {Math.round(score * 100) / 100}%
118 |
119 | ) : null}
120 |
123 |
124 |
130 | {firstname}{' '}
131 | {liked ? (
132 |
136 |
137 | 💗
138 |
139 |
140 | ) : null}
141 |
142 |
143 | {birthDate
144 | ? getAge(new Date(birthDate).toISOString().split('T')[0])
145 | : 'age not defined '}
146 | {location ? distance() : ''}
147 |
148 | {type === 'search' ? null : (
149 |
150 | {lastVisit}
151 |
152 | )}
153 |
154 |
155 |
156 | {popularityRate}%
157 | handleLike(visitor)}
162 | >
163 |
164 |
165 |
166 |
167 | );
168 | }
169 |
--------------------------------------------------------------------------------
/src/components/shared/profiles-grid.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import Grid from '@material-ui/core/Grid';
3 | import Container from '@material-ui/core/Container';
4 | import Box from '@material-ui/core/Box';
5 | import Typography from '@material-ui/core/Typography';
6 | import _ from 'lodash';
7 | import CircularProgress from '@material-ui/core/CircularProgress';
8 | import InfiniteScroll from 'react-infinite-scroll-component';
9 | import MediaCard from './components/media-card';
10 |
11 | const ProfilesGrid = ({
12 | classes,
13 | profiles,
14 | currentUserProfile,
15 | handleLike,
16 | type,
17 | }) => {
18 | const [displayedProfiles, setDisplayedProfiles] = useState([]);
19 | const displayMoreProfiles = () => {
20 | setDisplayedProfiles(profiles.slice(0, displayedProfiles.length + 20));
21 | };
22 | useEffect(() => {
23 | displayMoreProfiles();
24 | // eslint-disable-next-line
25 | }, [profiles]);
26 | if (_.isEmpty(profiles) && (type === "like" || type === "visit")) {
27 | return (
28 |
29 | Sorry, there is nothing to display yet 😢 . But don't worry, it's coming !
30 |
31 | )
32 | }
33 | return (
34 |
35 |
36 |
42 |
43 |
44 | }
45 | >
46 |
47 | {_.map(displayedProfiles, profile => (
48 |
57 |
63 |
64 | ))}
65 |
66 |
67 |
68 |
69 | );
70 | };
71 |
72 | export default ProfilesGrid;
73 |
--------------------------------------------------------------------------------
/src/components/shared/title.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import Typography from '@material-ui/core/Typography';
4 |
5 | const useStyles = makeStyles(theme => ({
6 | hero: {
7 | backgroundColor: '#f4f4f4',
8 | width: '100%',
9 | color: 'white',
10 | display: 'flex',
11 | justifyContent: 'center',
12 | paddingTop: theme.spacing(2),
13 | paddingBottom: theme.spacing(2),
14 | marginBottom: theme.spacing(4),
15 | },
16 | }));
17 |
18 | const Title = ({ textTitle }) => {
19 | const classes = useStyles();
20 |
21 | return (
22 |
23 |
24 | {textTitle}
25 |
26 |
27 | );
28 | };
29 |
30 | export default Title;
31 |
--------------------------------------------------------------------------------
/src/components/signup/index.js:
--------------------------------------------------------------------------------
1 | import Signup from './signup-view';
2 |
3 | export default Signup;
4 |
--------------------------------------------------------------------------------
/src/components/signup/signup-container.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { toast } from 'react-toastify';
3 | import axios from 'axios';
4 |
5 | const useSignUpForm = callback => {
6 | const [inputs, setInputs] = useState({
7 | firstname: '',
8 | surname: '',
9 | username: '',
10 | email: '',
11 | password1: '',
12 | password2: '',
13 | });
14 | const { firstname, surname, username, email, password1 } = inputs;
15 | const handleSubmit = event => {
16 | if (event) {
17 | event.preventDefault();
18 | axios
19 | .post(
20 | `${process.env.REACT_APP_PUBLIC_API_URL}/users`,
21 | {
22 | firstname,
23 | surname,
24 | username,
25 | email,
26 | password: password1,
27 | },
28 | {
29 | headers: {
30 | 'Content-type': 'application/json; charset=UTF-8',
31 | },
32 | },
33 | )
34 | .then(({ data }) => {
35 | if (data.created === true) {
36 | callback(true);
37 | } else {
38 | data.errors.forEach(error => {
39 | toast.error(error);
40 | });
41 | }
42 | });
43 | }
44 | };
45 | const handleInputChange = event => {
46 | event.persist();
47 | const newInput = {
48 | ...inputs,
49 | [event.target.name]: event.target.value,
50 | };
51 | setInputs(newInput);
52 | };
53 | return {
54 | handleSubmit,
55 | handleInputChange,
56 | inputs,
57 | };
58 | };
59 |
60 | export default useSignUpForm;
61 |
--------------------------------------------------------------------------------
/src/components/signup/signup-view.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Container from '@material-ui/core/Container';
3 | import Avatar from '@material-ui/core/Avatar';
4 | import LockOutlinedIcon from '@material-ui/icons/LockOutlined';
5 | import Typography from '@material-ui/core/Typography';
6 | import TextField from '@material-ui/core/TextField';
7 | import Button from '@material-ui/core/Button';
8 | import Grid from '@material-ui/core/Grid';
9 | import Link from '@material-ui/core/Link';
10 | import { makeStyles } from '@material-ui/core/styles';
11 |
12 | import useSignUpForm from './signup-container';
13 |
14 | const useStyles = makeStyles(theme => ({
15 | '@global': {
16 | body: {
17 | backgroundColor: theme.palette.common.white,
18 | },
19 | },
20 | paper: {
21 | marginTop: theme.spacing(8),
22 | display: 'flex',
23 | flexDirection: 'column',
24 | alignItems: 'center',
25 | },
26 | avatar: {
27 | margin: theme.spacing(1),
28 | backgroundColor: theme.palette.secondary.main,
29 | },
30 | form: {
31 | width: '100%', // Fix IE 11 issue.
32 | marginTop: theme.spacing(3),
33 | },
34 | submit: {
35 | margin: theme.spacing(3, 0, 2),
36 | },
37 | }));
38 |
39 | const Signup = () => {
40 | const signup = success => {
41 | if (success) {
42 | window.location = '/?message=signup_success';
43 | }
44 | };
45 | const { inputs, handleInputChange, handleSubmit } = useSignUpForm(signup);
46 | const classes = useStyles();
47 |
48 | return (
49 |
50 |
51 |
52 |
53 |
54 |
55 | Sign up
56 |
57 |
167 |
168 |
169 | );
170 | };
171 |
172 | export default Signup;
173 |
--------------------------------------------------------------------------------
/src/components/suggestions/index.js:
--------------------------------------------------------------------------------
1 | import Suggestions from './suggestions-view';
2 |
3 | export default Suggestions;
4 |
--------------------------------------------------------------------------------
/src/components/suggestions/suggestions-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState, useContext } from 'react';
3 | import { toast } from 'react-toastify';
4 | import _ from 'lodash';
5 | import { AuthContext } from '../app/AuthContext';
6 |
7 | const SuggestionsContainer = () => {
8 | const [loaded, setLoaded] = useState(false);
9 | const [currentUserProfile, setCurrentUserProfile] = useState({});
10 | const [suggestionsResult, setSuggestionsResult] = useState([]);
11 | const [suggestionsOptions, setSuggestionsOptions] = useState({
12 | ageRange: [18, 85],
13 | popularityRange: [0, 100],
14 | interests: [],
15 | distanceMax: 100,
16 | sort: 'score',
17 | });
18 | const { authContext } = useContext(AuthContext);
19 | const { userData, token } = authContext;
20 |
21 | const handleLike = likedId => {
22 | axios
23 | .get(
24 | `${process.env.REACT_APP_PUBLIC_API_URL}/likes/like-unlike/${likedId}`,
25 | {
26 | headers: {
27 | 'Content-type': 'application/json; charset=UTF-8',
28 | 'x-access-token': token,
29 | },
30 | },
31 | )
32 | .then(result => {
33 | if (result.data.blocked) {
34 | toast.error(result.data.message);
35 | } else {
36 | const indexToModify = _.keys(
37 | _.pickBy(suggestionsResult, { visitor: likedId }),
38 | );
39 |
40 | const newSuggestionsResult = suggestionsResult;
41 | indexToModify.forEach(index => {
42 | newSuggestionsResult[parseInt(index, 10)] = {
43 | ...newSuggestionsResult[parseInt(index, 10)],
44 | liking: !suggestionsResult[parseInt(index, 10)].liking,
45 | };
46 | });
47 | document
48 | .querySelectorAll(`[visitor*="${likedId}"]`)
49 | .forEach(element => {
50 | if (element.classList.contains('MuiIconButton-colorSecondary'))
51 | element.classList.remove('MuiIconButton-colorSecondary');
52 | else element.className += ' MuiIconButton-colorSecondary';
53 | });
54 | setSuggestionsResult(newSuggestionsResult);
55 | }
56 | });
57 | };
58 |
59 | const handleSort = (event, profiles = suggestionsResult) => {
60 | let sortChoice = '';
61 | if (event) {
62 | sortChoice = event.target.value;
63 | } else {
64 | sortChoice = suggestionsOptions.sort;
65 | }
66 | let order = '';
67 | switch (sortChoice) {
68 | case 'score':
69 | order = 'desc';
70 | break;
71 | case 'distance':
72 | order = 'asc';
73 | break;
74 | case 'ageAsc':
75 | order = 'asc';
76 | break;
77 | case 'ageDesc':
78 | order = 'desc';
79 | break;
80 | case 'popularity':
81 | order = 'desc';
82 | break;
83 | case 'interests':
84 | order = 'asc';
85 | break;
86 | default:
87 | }
88 | const newSuggestionsOptions = {
89 | ...suggestionsOptions,
90 | sort: sortChoice,
91 | };
92 | setSuggestionsOptions(newSuggestionsOptions);
93 | setSuggestionsResult(
94 | _.orderBy(
95 | profiles,
96 | [
97 | profile => {
98 | switch (sortChoice) {
99 | case 'score':
100 | return profile.score;
101 | case 'distance':
102 | return profile.distance;
103 | case 'ageAsc':
104 | return profile.age;
105 | case 'ageDesc':
106 | return profile.age;
107 | case 'popularity':
108 | return profile.popularityRate;
109 | case 'interests':
110 | return profile.interests[0] ? profile.interests[0] : 'ZZZZ';
111 | default:
112 | }
113 | },
114 | ],
115 | [order],
116 | ),
117 | );
118 | };
119 |
120 | const handleChangeSlider = (type, newValue) => {
121 | if (type === 'interests') {
122 | newValue = newValue.map(interest => {
123 | return interest.name;
124 | });
125 | const newSuggestionsOptions = { ...suggestionsOptions, [type]: newValue };
126 | setSuggestionsOptions(newSuggestionsOptions);
127 | fetchSuggestions(newSuggestionsOptions);
128 | return;
129 | }
130 | const newSuggestionsOptions = { ...suggestionsOptions, [type]: newValue };
131 | setSuggestionsOptions(newSuggestionsOptions);
132 | };
133 |
134 | const fetchSuggestions = (suggestionsQuery = suggestionsOptions) => {
135 | axios
136 | .post(
137 | `${process.env.REACT_APP_PUBLIC_API_URL}/users/suggestions`,
138 | suggestionsQuery,
139 | {
140 | headers: {
141 | 'Content-type': 'application/json; charset=UTF-8',
142 | 'x-access-token': token,
143 | },
144 | },
145 | )
146 | .then(result => {
147 | if (result.data.authorized === false) {
148 | window.location = '/profile?message=profile_not_completed';
149 | return;
150 | }
151 | handleSort(null, result.data);
152 | });
153 | };
154 |
155 | if (_.isEmpty(currentUserProfile) && loaded === false) {
156 | userData.then(value => {
157 | setCurrentUserProfile(value.data);
158 | fetchSuggestions();
159 | setLoaded(true);
160 | });
161 | }
162 | return {
163 | currentUserProfile,
164 | loaded,
165 | suggestionsResult,
166 | suggestionsOptions,
167 | handleChangeSlider,
168 | setSuggestionsOptions,
169 | fetchSuggestions,
170 | handleLike,
171 | handleSort,
172 | };
173 | };
174 |
175 | export default SuggestionsContainer;
176 |
--------------------------------------------------------------------------------
/src/components/suggestions/suggestions-view.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CircularProgress from '@material-ui/core/CircularProgress';
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import Divider from '@material-ui/core/Divider';
5 | import SuggestionsFilters from './components/suggestions-filters';
6 | import ProfilesGrid from '../shared/profiles-grid';
7 | import Title from '../shared/title';
8 | import SuggestionsContainer from './suggestions-container';
9 |
10 | const useStyles = makeStyles(theme => ({
11 | wrapper: {
12 | display: 'flex',
13 | flexDirection: 'row',
14 | justifyContent: 'center',
15 | marginLeft: 'auto',
16 | marginRight: 'auto',
17 | maxWidth: '1500px',
18 | marginTop: '10px',
19 | },
20 | center: {
21 | display: 'flex',
22 | flexDirection: 'column',
23 | justifyContent: 'center',
24 | alignItems: 'center',
25 | },
26 | progress: {
27 | height: '100vh',
28 | display: 'flex',
29 | flexDirection: 'row',
30 | justifyContent: 'center',
31 | alignItems: 'center',
32 | },
33 | filtersContainer: {
34 | width: '100%',
35 | display: 'flex',
36 | flexDirection: 'column',
37 | justifyContent: 'space-between',
38 | padding: '10px',
39 | },
40 | filtersButtonContainer: {
41 | width: '100%',
42 | display: 'flex',
43 | flexDirection: 'row',
44 | justifyContent: 'space-around',
45 | position: 'relative',
46 | },
47 | sliderContainer: {
48 | position: 'absolute',
49 | backgroundColor: 'red',
50 | },
51 | slider: {
52 | width: '100%',
53 | },
54 | menuItem: {
55 | width: '600px',
56 | height: '300px',
57 | textDecoration: 'none',
58 | },
59 | titleGutterbottom: {
60 | marginBottom: theme.spacing(2),
61 | },
62 | }));
63 |
64 | const Suggestions = () => {
65 | const {
66 | currentUserProfile,
67 | loaded,
68 | suggestionsResult,
69 | suggestionsOptions,
70 | handleChangeSlider,
71 | fetchSuggestions,
72 | handleLike,
73 | handleSort,
74 | } = SuggestionsContainer();
75 | const classes = useStyles();
76 |
77 | if (loaded === false) {
78 | return (
79 |
80 |
81 |
82 | );
83 | }
84 |
85 | return (
86 | <>
87 |
88 |
96 |
97 |
104 | >
105 | );
106 | };
107 |
108 | export default Suggestions;
109 |
--------------------------------------------------------------------------------
/src/components/toaster/index.js:
--------------------------------------------------------------------------------
1 | import Toaster from './toaster-view';
2 |
3 | export default Toaster;
4 |
--------------------------------------------------------------------------------
/src/components/toaster/toaster-container.js:
--------------------------------------------------------------------------------
1 | import { toast } from 'react-toastify';
2 |
3 | export const loginSuccess = () => {
4 | const toasterType = 'success';
5 | const toasterMessage = 'You successfully logged in!';
6 |
7 | toast(toasterMessage, { type: toasterType });
8 | };
9 |
10 | export const logoutSuccess = () => {
11 | const toasterType = 'success';
12 | const toasterMessage = 'You successfully logged out!';
13 |
14 | toast(toasterMessage, { type: toasterType });
15 | };
16 |
17 | export const deleteSuccess = () => {
18 | const toasterType = 'success';
19 | const toasterMessage = 'Your account was successfully deleted! Bye bye!';
20 |
21 | toast(toasterMessage, { type: toasterType });
22 | };
23 |
24 | export const signupSuccess = () => {
25 | const toasterType = 'success';
26 | const toasterMessage = 'You successfully signed up!';
27 |
28 | toast(toasterMessage, { type: toasterType });
29 | };
30 |
31 | export const alreadyLoggedin = () => {
32 | const toasterType = 'warning';
33 | const toasterMessage = 'You are already logged in!';
34 |
35 | toast(toasterMessage, { type: toasterType });
36 | };
37 |
38 | export const userNotFound = () => {
39 | const toasterType = 'warning';
40 | const toasterMessage = "This user doesn't exist!";
41 |
42 | toast(toasterMessage, { type: toasterType });
43 | };
44 |
45 | export const userValidated = () => {
46 | const toasterType = 'success';
47 | const toasterMessage = 'Your account is successfully validated!';
48 |
49 | toast(toasterMessage, { type: toasterType });
50 | };
51 | export const userNotValidated = () => {
52 | const toasterType = 'warning';
53 | const toasterMessage = 'The validation link is not valid!';
54 |
55 | toast(toasterMessage, { type: toasterType });
56 | };
57 | export const forgotPasswordSuccess = () => {
58 | const toasterType = 'success';
59 | const toasterMessage = 'You have receive a reset link inside your email';
60 |
61 | toast(toasterMessage, { type: toasterType });
62 | };
63 |
64 | export const resetPasswordSuccess = () => {
65 | const toasterType = 'success';
66 | const toasterMessage = 'You have successfully reset the password';
67 |
68 | toast(toasterMessage, { type: toasterType });
69 | };
70 |
71 | export const profileNotCompleted = () => {
72 | const toasterType = 'warning';
73 | const toasterMessage =
74 | 'You need to complete your profile in order to access to other profiles';
75 | toast(toasterMessage, { type: toasterType });
76 | };
77 |
78 | export const userBlockedYou = () => {
79 | const toasterType = 'warning';
80 | const toasterMessage = 'You have been blocked by this user!';
81 | toast(toasterMessage, { type: toasterType });
82 | };
83 |
84 | export const accessDenied = () => {
85 | const toasterType = 'error';
86 | const toasterMessage = "You can't access this chatroom. Sorry!";
87 | toast(toasterMessage, { type: toasterType });
88 | };
89 |
--------------------------------------------------------------------------------
/src/components/toaster/toaster-view.js:
--------------------------------------------------------------------------------
1 | import useDebouncedCallback from 'use-debounce/lib/useDebouncedCallback';
2 | import {
3 | loginSuccess,
4 | logoutSuccess,
5 | signupSuccess,
6 | alreadyLoggedin,
7 | userValidated,
8 | userNotValidated,
9 | forgotPasswordSuccess,
10 | resetPasswordSuccess,
11 | deleteSuccess,
12 | userNotFound,
13 | profileNotCompleted,
14 | userBlockedYou,
15 | accessDenied,
16 | } from './toaster-container';
17 |
18 | const Toaster = ({ getParams }) => {
19 | const [toastDebounced] = useDebouncedCallback(getParams => {
20 | switch (getParams.message) {
21 | case 'login_success':
22 | loginSuccess();
23 | break;
24 | case 'logout_success':
25 | logoutSuccess();
26 | break;
27 | case 'delete_success':
28 | deleteSuccess();
29 | break;
30 | case 'signup_success':
31 | signupSuccess();
32 | break;
33 | case 'already_loggedin':
34 | alreadyLoggedin();
35 | break;
36 | case 'user_validated':
37 | userValidated();
38 | break;
39 | case 'user_not_validated':
40 | userNotValidated();
41 | break;
42 | case 'forgotPassword_success':
43 | forgotPasswordSuccess();
44 | break;
45 | case 'reset_password_success':
46 | resetPasswordSuccess();
47 | break;
48 | case 'user_not_found':
49 | userNotFound();
50 | break;
51 | case 'profile_not_completed':
52 | profileNotCompleted();
53 | break;
54 | case 'user_blocked_you':
55 | userBlockedYou();
56 | break;
57 | case 'access_denied':
58 | accessDenied();
59 | break;
60 | case 'cant_access_chat':
61 | accessDenied();
62 | break;
63 | default:
64 | break;
65 | }
66 | }, 500);
67 |
68 | if (getParams !== undefined && getParams.message !== undefined) {
69 | toastDebounced(getParams);
70 | }
71 | return null;
72 | };
73 |
74 | export default Toaster;
75 |
--------------------------------------------------------------------------------
/src/components/uservalidation/index.js:
--------------------------------------------------------------------------------
1 | import UserValidation from './uservalidation-view';
2 |
3 | export default UserValidation;
4 |
--------------------------------------------------------------------------------
/src/components/uservalidation/uservalidation-view.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import React from 'react';
3 | import PropTypes from 'prop-types';
4 |
5 | const UserValidation = ({ computedMatch }) => {
6 | const { token } = computedMatch.params;
7 | axios
8 | .get(
9 | `${process.env.REACT_APP_PUBLIC_API_URL}/validation/newaccount/${token}`,
10 | {
11 | headers: {
12 | 'Content-type': 'application/json; charset=UTF-8',
13 | },
14 | },
15 | )
16 | .then(data => {
17 | if (data.data.success) {
18 | window.location = '/?message=user_validated';
19 | } else {
20 | window.location = '/?message=user_not_validated';
21 | }
22 | });
23 | return Chargement en cours ;
24 | };
25 |
26 | UserValidation.propTypes = {
27 | // eslint-disable-next-line react/forbid-prop-types
28 | computedMatch: PropTypes.object.isRequired,
29 | };
30 |
31 | export default UserValidation;
32 |
--------------------------------------------------------------------------------
/src/components/visit/index.js:
--------------------------------------------------------------------------------
1 | import Visit from './visit-view';
2 |
3 | export default Visit;
4 |
--------------------------------------------------------------------------------
/src/components/visit/visit-container.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { useState, useContext } from 'react';
3 | import _ from 'lodash';
4 | import { toast } from 'react-toastify';
5 | import { AuthContext } from '../app/AuthContext';
6 |
7 | const VisitContainer = () => {
8 | const [loaded, setLoaded] = useState(false);
9 | const [visitedProfile, setVisitedProfile] = useState({});
10 | const [visitorProfile, setVisitorProfile] = useState({});
11 | const { authContext } = useContext(AuthContext);
12 | const { userData, token } = authContext;
13 |
14 | const handleLike = likedId => {
15 | axios
16 | .get(
17 | `${process.env.REACT_APP_PUBLIC_API_URL}/likes/like-unlike/${likedId}`,
18 | {
19 | headers: {
20 | 'Content-type': 'application/json; charset=UTF-8',
21 | 'x-access-token': token,
22 | },
23 | },
24 | )
25 | .then(result => {
26 | if (result.data.blocked) {
27 | toast.error(result.data.message);
28 | } else {
29 | const indexToModify = _.keys(
30 | _.pickBy(visitedProfile, { visitor: likedId }),
31 | );
32 |
33 | const newVisitedProfile = visitedProfile;
34 | indexToModify.forEach(index => {
35 | newVisitedProfile[parseInt(index, 10)] = {
36 | ...newVisitedProfile[parseInt(index, 10)],
37 | liking: !visitedProfile[parseInt(index, 10)].liking,
38 | };
39 | });
40 | document
41 | .querySelectorAll(`[visitor*="${likedId}"]`)
42 | .forEach(element => {
43 | if (element.classList.contains('MuiIconButton-colorSecondary'))
44 | element.classList.remove('MuiIconButton-colorSecondary');
45 | else element.className += ' MuiIconButton-colorSecondary';
46 | });
47 | setVisitedProfile(newVisitedProfile);
48 | }
49 | });
50 | };
51 |
52 | const fetchVisitHistory = () =>
53 | axios
54 | .get(`${process.env.REACT_APP_PUBLIC_API_URL}/visits/`, {
55 | headers: {
56 | 'Content-type': 'application/json; charset=UTF-8',
57 | 'x-access-token': token,
58 | },
59 | })
60 | .then(response => {
61 | return response.data;
62 | })
63 | .catch(error => {
64 | if (process.env.REACT_APP_VERBOSE === 'true') console.log(error);
65 | });
66 |
67 | if (_.isEmpty(visitedProfile) && loaded === false) {
68 | Promise.all([userData, fetchVisitHistory()]).then(values => {
69 | setVisitedProfile(values[1]);
70 | setVisitorProfile(values[0].data);
71 | setLoaded(true);
72 | });
73 | }
74 |
75 | return { visitedProfile, visitorProfile, loaded, handleLike };
76 | };
77 |
78 | export default VisitContainer;
79 |
--------------------------------------------------------------------------------
/src/components/visit/visit-view.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import CircularProgress from '@material-ui/core/CircularProgress';
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import VisitContainer from './visit-container';
5 | import Title from '../shared/title';
6 | import ProfilesGrid from '../shared/profiles-grid';
7 |
8 | const useStyles = makeStyles(theme => ({
9 | wrapper: {
10 | display: 'flex',
11 | flexDirection: 'row',
12 | justifyContent: 'center',
13 | marginLeft: 'auto',
14 | marginRight: 'auto',
15 | maxWidth: '1500px',
16 | marginTop: '10px',
17 | },
18 | center: {
19 | display: 'flex',
20 | flexDirection: 'column',
21 | justifyContent: 'center',
22 | alignItems: 'center',
23 | },
24 | progress: {
25 | height: '100vh',
26 | display: 'flex',
27 | flexDirection: 'row',
28 | justifyContent: 'center',
29 | alignItems: 'center',
30 | },
31 | emptyPageWrapper: {
32 | display: 'flex',
33 | flexDirection: 'row',
34 | justifyContent: 'center',
35 | },
36 | emptyPageText: {
37 | maxWidth: "700px",
38 | margin: theme.spacing(2),
39 | },
40 | }));
41 |
42 | const Visit = ({ computedMatch }) => {
43 | const visitedUsername = computedMatch.params.username;
44 |
45 | const { visitedProfile, visitorProfile, loaded, handleLike } = VisitContainer(
46 | visitedUsername,
47 | );
48 | const classes = useStyles();
49 |
50 | if (loaded === false) {
51 | return (
52 |
53 |
54 |
55 | );
56 | }
57 |
58 | return (
59 | <>
60 |
61 |
68 | >
69 | );
70 | };
71 |
72 | export default Visit;
73 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /* body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
12 | monospace;
13 | } */
14 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | // import './components/app/index.css';
4 | import App from './components/app/App';
5 | import * as serviceWorker from './serviceWorker';
6 |
7 | // ReactDOM.render( , document.getElementById('root'));
8 | ReactDOM.render( , document.getElementById('root'));
9 |
10 | // If you want your app to work offline and load faster, you can change
11 | // unregister() to register() below. Note this comes with some pitfalls.
12 | // Learn more about service workers: https://bit.ly/CRA-PWA
13 | serviceWorker.unregister();
14 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | /* eslint-disable no-param-reassign */
3 | /* eslint-disable no-use-before-define */
4 | // This optional code is used to register a service worker.
5 | // register() is not called by default.
6 |
7 | // This lets the app load faster on subsequent visits in production, and gives
8 | // it offline capabilities. However, it also means that developers (and users)
9 | // will only see deployed updates on subsequent visits to a page, after all the
10 | // existing tabs open on the page have been closed, since previously cached
11 | // resources are updated in the background.
12 |
13 | // To learn more about the benefits of this model and instructions on how to
14 | // opt-in, read https://bit.ly/CRA-PWA
15 |
16 | const isLocalhost = Boolean(
17 | window.location.hostname === 'localhost' ||
18 | // [::1] is the IPv6 localhost address.
19 | window.location.hostname === '[::1]' ||
20 | // 127.0.0.1/8 is considered localhost for IPv4.
21 | window.location.hostname.match(
22 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/,
23 | ),
24 | );
25 |
26 | export function register(config) {
27 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
28 | // The URL constructor is available in all browsers that support SW.
29 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
30 | if (publicUrl.origin !== window.location.origin) {
31 | // Our service worker won't work if PUBLIC_URL is on a different origin
32 | // from what our page is served on. This might happen if a CDN is used to
33 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
34 | return;
35 | }
36 |
37 | window.addEventListener('load', () => {
38 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
39 |
40 | if (isLocalhost) {
41 | // This is running on localhost. Let's check if a service worker still exists or not.
42 | checkValidServiceWorker(swUrl, config);
43 |
44 | // Add some additional logging to localhost, pointing developers to the
45 | // service worker/PWA documentation.
46 | navigator.serviceWorker.ready.then(() => {
47 | if (process.env.REACT_APP_VERBOSE === 'true')
48 | console.log(
49 | 'This web app is being served cache-first by a service ' +
50 | 'worker. To learn more, visit https://bit.ly/CRA-PWA',
51 | );
52 | });
53 | } else {
54 | // Is not localhost. Just register service worker
55 | registerValidSW(swUrl, config);
56 | }
57 | });
58 | }
59 | }
60 |
61 | function registerValidSW(swUrl, config) {
62 | navigator.serviceWorker
63 | .register(swUrl)
64 | .then(registration => {
65 | registration.onupdatefound = () => {
66 | const installingWorker = registration.installing;
67 | if (installingWorker == null) {
68 | return;
69 | }
70 | installingWorker.onstatechange = () => {
71 | if (installingWorker.state === 'installed') {
72 | if (navigator.serviceWorker.controller) {
73 | // At this point, the updated precached content has been fetched,
74 | // but the previous service worker will still serve the older
75 | // content until all client tabs are closed.
76 |
77 | if (process.env.REACT_APP_VERBOSE === 'true')
78 | console.log(
79 | 'New content is available and will be used when all ' +
80 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.',
81 | );
82 |
83 | // Execute callback
84 | if (config && config.onUpdate) {
85 | config.onUpdate(registration);
86 | }
87 | } else {
88 | // At this point, everything has been precached.
89 | // It's the perfect time to display a
90 | // "Content is cached for offline use." message.
91 | if (process.env.REACT_APP_VERBOSE === 'true')
92 | console.log('Content is cached for offline use.');
93 |
94 | // Execute callback
95 | if (config && config.onSuccess) {
96 | config.onSuccess(registration);
97 | }
98 | }
99 | }
100 | };
101 | };
102 | })
103 | .catch(error => {
104 | console.error('Error during service worker registration:', error);
105 | });
106 | }
107 |
108 | function checkValidServiceWorker(swUrl, config) {
109 | // Check if the service worker can be found. If it can't reload the page.
110 | fetch(swUrl)
111 | .then(response => {
112 | // Ensure service worker exists, and that we really are getting a JS file.
113 | const contentType = response.headers.get('content-type');
114 | if (
115 | response.status === 404 ||
116 | (contentType != null && contentType.indexOf('javascript') === -1)
117 | ) {
118 | // No service worker found. Probably a different app. Reload the page.
119 | navigator.serviceWorker.ready.then(registration => {
120 | registration.unregister().then(() => {
121 | window.location.reload();
122 | });
123 | });
124 | } else {
125 | // Service worker found. Proceed as normal.
126 | registerValidSW(swUrl, config);
127 | }
128 | })
129 | .catch(() => {
130 | if (process.env.REACT_APP_VERBOSE === 'true')
131 | console.log(
132 | 'No internet connection found. App is running in offline mode.',
133 | );
134 | });
135 | }
136 |
137 | export function unregister() {
138 | if ('serviceWorker' in navigator) {
139 | navigator.serviceWorker.ready.then(registration => {
140 | registration.unregister();
141 | });
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/todo.MD:
--------------------------------------------------------------------------------
1 | # TODO:
2 |
3 | - Proteger les requetes avec try et catch
4 | A checker au signup :
5 |
6 | * tout les champs sont remplis en back
7 | * Le login, l'email n'existe pas en DB
8 | * password 1 et 2 sont similaires
9 | * Mot de passe securisé majuscule, minuscule, chiffre, charactere special et minimum 8 characteres
10 | * verifier protection injection sql, cross cxx
11 | * Ajouter messages erreur pour chaque champ
12 | * Verifier les inputs onKeyUp
13 | CORS Errors
14 |
15 | Unsubscribe les composant quand on exit
16 | => sur les hooks
17 |
18 | ## 10/11
19 |
20 | - integrer front material-ui sur les pages signup et login => SEGO
21 | - faire front page mon profil => SEGO
22 | - Encrypter les mots de passe
23 | - validation compte via email https://trello.com/c/YhooitkC/10-envoyer-email-de-confirmation-pour-valider-profil => YANN
24 | - reset mdp https://trello.com/c/JMxZst4c/13-reinitialisation-mdp => YANN
25 |
26 | ## 14/11
27 |
28 | ### Verification token: OK
29 |
30 | L'utilisateur recoit un lien pour confirmer le compte
31 | quand le front recoit l'URL, il fait une requete au back pour confirmer le token
32 | Si le back retourne true, le front affiche "Utilisateur validé". Le back supprime la ligne dans userValidation et passe utilisateur a verfied => true
33 | Si le back retourne false, le front affiche l'erreur que le back aura retourné
34 |
35 | a faire:
36 | Au login, si utilisateur n'a pas validé son compte, il faut envoyer message d'erreur "compte non validé et renvoyer un email de validation"
37 | Afficher message d'erreur en front : ajouter type d'erreur et changer le parsing d'erreur
38 |
--------------------------------------------------------------------------------