auth0 authentication & sample profile page

This commit is contained in:
Ruidy Nemausat 2020-01-31 11:00:19 +01:00
parent 12fdc42bd5
commit a649006d87
19 changed files with 263 additions and 339 deletions

2
.gitignore vendored
View file

@ -21,3 +21,5 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
/src/utils/auth_config.json

View file

@ -71,8 +71,8 @@ Free meal planner for cooks short on ideas! (like me …)
- [Materialize](https://materializecss.com) CSS librairy for styling
- Public API: [TheMealDb](https://www.themealdb.com/api.php) and [TheCocktailDb](https://www.thecocktaildb.com/api.php)
- Hosting: [Render](https://render.com/)
- Authentication : [Auth0](https://auth0.com/)
- Analytics : Google Analytics & Mixpanel
- Authentication : Firebase or Auth0
## Versions
@ -87,6 +87,7 @@ Free meal planner for cooks short on ideas! (like me …)
- Progressive Web App
- User Interface Enhancement
- Secured User Profiles
- Contact form
## TO DO

256
package-lock.json generated
View file

@ -4,6 +4,19 @@
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@auth0/auth0-spa-js": {
"version": "1.6.3",
"resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.6.3.tgz",
"integrity": "sha512-jbK6qrcy3N8bAltP/sEdv9vo0eML9LneAESB05K/MPCwZVUJN9hu+y42DxfI7kH6dnVNyUbJohgpimv+eNonlQ==",
"requires": {
"browser-tabs-lock": "^1.2.1",
"core-js": "^3.2.1",
"es-cookie": "^1.2.0",
"fast-text-encoding": "^1.0.0",
"promise-polyfill": "^8.1.3",
"unfetch": "^4.1.0"
}
},
"@babel/code-frame": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
@ -1043,11 +1056,6 @@
"resolved": "https://registry.npmjs.org/@csstools/normalize.css/-/normalize.css-10.1.0.tgz",
"integrity": "sha512-ij4wRiunFfaJxjB0BdrYHIH8FxBJpOwNPhhAcunlmPdXudL1WQV1qoP9un6JsEBAgQH+7UXyyjh0g7jTxXK6tg=="
},
"@emotion/hash": {
"version": "0.7.4",
"resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.7.4.tgz",
"integrity": "sha512-fxfMSBMX3tlIbKUdtGKxqB1fyrH6gVrX39Gsv3y8lRYKUqlgDt3UMqQyGnR1bQMa2B8aGnhLZokZgg8vT0Le+A=="
},
"@hapi/address": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-2.1.4.tgz",
@ -1273,75 +1281,6 @@
"@types/yargs": "^13.0.0"
}
},
"@material-ui/core": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.0.tgz",
"integrity": "sha512-zrrr8mPU5DDBYaVil4uJYauW41PjSn5otn7cqGsmWOY0t90fypr9nNgM7rRJaPz2AP6oRSDx1kBQt2igf5uelg==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/styles": "^4.9.0",
"@material-ui/system": "^4.7.1",
"@material-ui/types": "^5.0.0",
"@material-ui/utils": "^4.7.1",
"@types/react-transition-group": "^4.2.0",
"clsx": "^1.0.2",
"convert-css-length": "^2.0.1",
"hoist-non-react-statics": "^3.2.1",
"normalize-scroll-left": "^0.2.0",
"popper.js": "^1.14.1",
"prop-types": "^15.7.2",
"react-is": "^16.8.0",
"react-transition-group": "^4.3.0"
}
},
"@material-ui/styles": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.0.tgz",
"integrity": "sha512-nJHum4RqYBPWsjL/9JET8Z02FZ9gSizlg/7LWVFpIthNzpK6OQ5OSRR4T4x9/p+wK3t1qNn3b1uI4XpnZaPxOA==",
"requires": {
"@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.7.4",
"@material-ui/types": "^5.0.0",
"@material-ui/utils": "^4.7.1",
"clsx": "^1.0.2",
"csstype": "^2.5.2",
"hoist-non-react-statics": "^3.2.1",
"jss": "^10.0.3",
"jss-plugin-camel-case": "^10.0.3",
"jss-plugin-default-unit": "^10.0.3",
"jss-plugin-global": "^10.0.3",
"jss-plugin-nested": "^10.0.3",
"jss-plugin-props-sort": "^10.0.3",
"jss-plugin-rule-value-function": "^10.0.3",
"jss-plugin-vendor-prefixer": "^10.0.3",
"prop-types": "^15.7.2"
}
},
"@material-ui/system": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.7.1.tgz",
"integrity": "sha512-zH02p+FOimXLSKOW/OT2laYkl9bB3dD1AvnZqsHYoseUaq0aVrpbl2BGjQi+vJ5lg8w73uYlt9zOWzb3+1UdMQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.7.1",
"prop-types": "^15.7.2"
}
},
"@material-ui/types": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@material-ui/types/-/types-5.0.0.tgz",
"integrity": "sha512-UeH2BuKkwDndtMSS0qgx1kCzSMw+ydtj0xx/XbFtxNSTlXydKwzs5gVW5ZKsFlAkwoOOQ9TIsyoCC8hq18tOwg=="
},
"@material-ui/utils": {
"version": "4.7.1",
"resolved": "https://registry.npmjs.org/@material-ui/utils/-/utils-4.7.1.tgz",
"integrity": "sha512-+ux0SlLdlehvzCk2zdQ3KiS3/ylWvuo/JwAGhvb8dFVvwR21K28z0PU9OQW2PGogrMEdvX3miEI5tGxTwwWiwQ==",
"requires": {
"@babel/runtime": "^7.4.4",
"prop-types": "^15.7.2",
"react-is": "^16.8.0"
}
},
"@mrmlnc/readdir-enhanced": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz",
@ -1650,14 +1589,6 @@
"@types/react": "*"
}
},
"@types/react-transition-group": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.2.3.tgz",
"integrity": "sha512-Hk8jiuT7iLOHrcjKP/ZVSyCNXK73wJAUz60xm0mVhiRujrdiI++j4duLiL282VGxwAgxetHQFfqA29LgEeSkFA==",
"requires": {
"@types/react": "*"
}
},
"@types/stack-utils": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
@ -2876,6 +2807,11 @@
}
}
},
"browser-tabs-lock": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.6.tgz",
"integrity": "sha512-+FUo97nnOix/nQo+j4HyCO47bsJtfx1EFhj1rghqYx7N0MQ8D34aoP4eJGrMtN0D18s2OAGpbPqo/0ONGxDLiA=="
},
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
@ -3794,11 +3730,6 @@
"shallow-clone": "^0.1.2"
}
},
"clsx": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.0.4.tgz",
"integrity": "sha512-1mQ557MIZTrL/140j+JVdRM6e31/OA4vTYxXgqIIZlndyfjHpyawKZia1Im05Vp9BWmImkcNrNtFYQMyFcgJDg=="
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@ -4010,11 +3941,6 @@
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA=="
},
"convert-css-length": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/convert-css-length/-/convert-css-length-2.0.1.tgz",
"integrity": "sha512-iGpbcvhLPRKUbBc0Quxx7w/bV14AC3ItuBEGMahA5WTYqB8lq9jH0kTXFheCBASsYnqeMFZhiTruNxr1N59Axg=="
},
"convert-source-map": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
@ -4301,15 +4227,6 @@
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz",
"integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY="
},
"css-vendor": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/css-vendor/-/css-vendor-2.0.7.tgz",
"integrity": "sha512-VS9Rjt79+p7M0WkPqcAza4Yq1ZHrsHrwf7hPL/bjQB+c1lwmAI+1FXxYTYt818D/50fFVflw0XKleiBN5RITkg==",
"requires": {
"@babel/runtime": "^7.6.2",
"is-in-browser": "^1.0.2"
}
},
"css-what": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-3.2.1.tgz",
@ -4736,15 +4653,6 @@
"utila": "~0.4"
}
},
"dom-helpers": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.1.3.tgz",
"integrity": "sha512-nZD1OtwfWGRBWlpANxacBEZrEuLa16o1nh7YopFWeoF68Zt8GGEmzHu6Xv4F3XaFIC+YXtTLrzgqKxFgLEe4jw==",
"requires": {
"@babel/runtime": "^7.6.3",
"csstype": "^2.6.7"
}
},
"dom-serializer": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz",
@ -4992,6 +4900,11 @@
"string.prototype.trimright": "^2.1.1"
}
},
"es-cookie": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/es-cookie/-/es-cookie-1.3.2.tgz",
"integrity": "sha512-UTlYYhXGLOy05P/vKVT2Ui7WtC7NiRzGtJyAKKn32g5Gvcjn7KAClLPWlipCtxIus934dFg9o9jXiBL0nP+t9Q=="
},
"es-to-primitive": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
@ -5855,6 +5768,11 @@
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"fast-text-encoding": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz",
"integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ=="
},
"faye-websocket": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
@ -6718,11 +6636,6 @@
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
"hyphenate-style-name": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz",
"integrity": "sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ=="
},
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@ -7022,11 +6935,6 @@
"is-extglob": "^2.1.1"
}
},
"is-in-browser": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz",
"integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU="
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
@ -8371,83 +8279,6 @@
"verror": "1.10.0"
}
},
"jss": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss/-/jss-10.0.3.tgz",
"integrity": "sha512-AcDvFdOk16If9qvC9KN3oFXsrkHWM9+TaPMpVB9orm3z+nq1Xw3ofHyflRe/mkSucRZnaQtlhZs1hdP3DR9uRw==",
"requires": {
"@babel/runtime": "^7.3.1",
"csstype": "^2.6.5",
"is-in-browser": "^1.1.3",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-camel-case": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.0.3.tgz",
"integrity": "sha512-rild/oFKFkmRP7AoiX9D6bdDAUfmJv8c7sEBvFoi+JP31dn2W8nw4txMKGnV1LJKlFkYprdZt1X99Uvztl1hug==",
"requires": {
"@babel/runtime": "^7.3.1",
"hyphenate-style-name": "^1.0.3",
"jss": "^10.0.3"
}
},
"jss-plugin-default-unit": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.0.3.tgz",
"integrity": "sha512-n+XfVLPF9Qh7IOTdQ8M4oRpjpg6egjr/r0NNytubbCafMgCILJYIVrMTGgOTydH+uvak8onQY3f/F9hasPUx6g==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "^10.0.3"
}
},
"jss-plugin-global": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.0.3.tgz",
"integrity": "sha512-kNotkAciJIXpIGYnmueaIifBne9rdq31O8Xq1nF7KMfKlskNRANTcEX5rVnsGKl2yubTMYfjKBFCeDgcQn6+gA==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "^10.0.3"
}
},
"jss-plugin-nested": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.0.3.tgz",
"integrity": "sha512-OMucRs9YLvWlZ3Ew+VhdgNVMwSS2zZy/2vy+s/etvopnPUzDHgCnJwdY2Wx/SlhLGERJeKKufyih2seH+ui0iw==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "^10.0.3",
"tiny-warning": "^1.0.2"
}
},
"jss-plugin-props-sort": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.0.3.tgz",
"integrity": "sha512-ufhvdCMnRcDa0tNHoZ12OcVNQQyE10yLMohxo/UIMarLV245rM6n9D19A12epjldRgyiS13SoSyLFCJEobprYg==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "^10.0.3"
}
},
"jss-plugin-rule-value-function": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.0.3.tgz",
"integrity": "sha512-RWwIT2UBAIwf3f6DQtt5gyjxHMRJoeO9TQku+ueR8dBMakqSSe8vFwQNfjXEoe0W+Tez5HZCTkZKNMulv3Z+9A==",
"requires": {
"@babel/runtime": "^7.3.1",
"jss": "^10.0.3"
}
},
"jss-plugin-vendor-prefixer": {
"version": "10.0.3",
"resolved": "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.0.3.tgz",
"integrity": "sha512-zVs6e5z4tFRK/fJ5kuTLzXlTFQbLeFTVwk7lTZiYNufmZwKT0kSmnOJDUukcSe7JLGSRztjWhnHB/6voP174gw==",
"requires": {
"@babel/runtime": "^7.3.1",
"css-vendor": "^2.0.7",
"jss": "^10.0.3"
}
},
"jsx-ast-utils": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
@ -9317,11 +9148,6 @@
"resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
"integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI="
},
"normalize-scroll-left": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/normalize-scroll-left/-/normalize-scroll-left-0.2.0.tgz",
"integrity": "sha512-t5oCENZJl8TGusJKoCJm7+asaSsPuNmK6+iEjrZ5TyBj2f02brCRsd4c83hwtu+e5d4LCSBZ0uoDlMjBo+A8yA=="
},
"normalize-url": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",
@ -9919,11 +9745,6 @@
"ts-pnp": "^1.1.2"
}
},
"popper.js": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz",
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
},
"portfinder": {
"version": "1.0.25",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz",
@ -10972,6 +10793,11 @@
"resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
"integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM="
},
"promise-polyfill": {
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz",
"integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g=="
},
"prompts": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.0.tgz",
@ -11438,17 +11264,6 @@
"workbox-webpack-plugin": "4.3.1"
}
},
"react-transition-group": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.3.0.tgz",
"integrity": "sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw==",
"requires": {
"@babel/runtime": "^7.5.5",
"dom-helpers": "^5.0.1",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2"
}
},
"read-pkg": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
@ -13319,6 +13134,11 @@
}
}
},
"unfetch": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.1.0.tgz",
"integrity": "sha512-crP/n3eAPUJxZXM9T80/yv0YhkTEx2K1D3h7D1AJM6fzsWZrxdyRuLN0JH/dkZh1LNH8LxCnBzoPFCPbb2iGpg=="
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",

View file

@ -3,6 +3,7 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@auth0/auth0-spa-js": "^1.6.3",
"@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.4.0",
"@testing-library/user-event": "^7.2.1",

View file

@ -1,6 +1,7 @@
import React, { useState } from "react";
import { Router } from "./utils/router";
import { Switch, Route, Redirect } from "react-router-dom";
import { useAuth0 } from "./utils/auth0-spa";
import { Home } from "./pages/Home";
import { Meal } from "./pages/Meal";
import { SearchPage } from "./pages/Search";
@ -13,8 +14,12 @@ import { SearchBar } from "./components/SearchBar";
import { Footer } from "./components/Footer";
import "./index.css";
import { getData } from "./utils/methods";
import history from "./utils/history";
import { Profile } from "./pages/Profile";
import { PrivateRoute } from "./components/PrivateRoute";
export const App = () => {
const { loading } = useAuth0();
const [searchString, setSearchString] = useState("");
const [categories, setCategories] = useState({ categories: [] });
const [searchResults, setSearchResults] = useState({ meals: [] });
@ -103,10 +108,13 @@ export const App = () => {
const buttonUrl = "/random";
return (
<Router>
<Navbar handleClick={getRandomMeal} buttonUrl={buttonUrl} />
return loading ? (
<div>Loading</div>
) : (
<Router history={history}>
<header>
<Navbar handleClick={getRandomMeal} buttonUrl={buttonUrl} />
</header>
<SearchBar
searchString={searchString}
handleChange={handleChange}
@ -118,6 +126,10 @@ export const App = () => {
<Home buttonUrl={buttonUrl} />
</Route>
<PrivateRoute exact path="/profile">
<Profile />
</PrivateRoute>
<Route exact path={buttonUrl}>
{meal !== undefined && meal.meals !== null ? (
<Meal meal={meal} getMeal={getRandomMeal} />

View file

@ -1,7 +1,7 @@
import React, { useState } from "react";
import React from "react";
export const ContactForm = ({ handleSubmit }) => {
const fields = ["firstname", "lastname", "email", "phone", "message"];
// const fields = ["firstname", "lastname", "email", "phone", "message"];
// const [firstName, setFirstName] = useState("");

View file

@ -0,0 +1,14 @@
import React from "react";
import { useAuth0 } from "../utils/auth0-spa";
export const LogInButton = () => {
const { loginWithRedirect } = useAuth0();
const handleClick = () => {
loginWithRedirect({});
};
return (
<button className="waves-effect waves-light btn" onClick={handleClick}>
Log in
</button>
);
};

View file

@ -0,0 +1,14 @@
import React from "react";
import { useAuth0 } from "../utils/auth0-spa";
export const LogOutButton = () => {
const { logout } = useAuth0();
const handleClick = () => {
logout();
};
return (
<button className="waves-effect waves-teal btn-flat" onClick={handleClick}>
Log out
</button>
);
};

View file

@ -1,21 +1,25 @@
import React from "react";
import { useAuth0 } from "../utils/auth0-spa";
import { Logo } from "./Logo";
import { RandomButton } from "./RandomButton";
import { FooterLink } from "./FooterLink";
import { LogInButton } from "./LogInButton";
import { LogOutButton } from "./LogOutButton";
export const Navbar = props => {
const { isAuthenticated } = useAuth0();
const links = ["categories", "contact"];
return (
<div className="row">
<nav>
<div className="nav-wrapper">
<div className="container">
<div className="container ">
<Logo />
<ul id="nav-mobile" className="right hide-on-med-and-down">
{links.map((link, i) => (
<FooterLink key={i} link={link} />
))}
<li>{isAuthenticated && <FooterLink link="profile" />}</li>
<li>
<RandomButton
handleClick={props.handleClick}
@ -23,6 +27,7 @@ export const Navbar = props => {
size="small"
/>
</li>
<li>{!isAuthenticated ? <LogInButton /> : <LogOutButton />}</li>
</ul>
</div>
</div>

View file

@ -0,0 +1,25 @@
import React, { useEffect } from "react";
import { Route } from "react-router-dom";
import { useAuth0 } from "../utils/auth0-spa";
export const PrivateRoute = ({ component: Component, path, ...rest }) => {
const { loading, isAuthenticated, loginWithRedirect } = useAuth0();
useEffect(() => {
if (loading || isAuthenticated) {
// catches infinite loading when no user is logged in
return;
}
const fn = async () => {
await loginWithRedirect({
appState: { targetUrl: path }
});
};
fn();
}, [loading, isAuthenticated, loginWithRedirect, path]);
const render = props =>
isAuthenticated === true ? <Component {...props} /> : null;
return <Route path={path} render={render} {...rest} />;
};

View file

@ -3,7 +3,28 @@ import ReactDOM from "react-dom";
import "./index.css";
import { App } from "./App";
import * as serviceWorker from "./serviceWorker";
import { Auth0Provider } from "./utils/auth0-spa";
import history from "./utils/history";
import config from "./utils/auth_config.json"; // for safety reasons this file is not tracked by git
ReactDOM.render(<App />, document.getElementById("root"));
const onRedirectCallBack = appState => {
history.push(
appState && appState.targetUrl
? appState.targetUrl
: window.location.pathname
);
};
ReactDOM.render(
<Auth0Provider
domain={config.domain}
client_id={config.clientId}
redirect_uri={window.location.origin}
onRedirectCallBack={onRedirectCallBack}
>
<App />
</Auth0Provider>,
document.getElementById("root")
);
serviceWorker.register();

View file

@ -12,6 +12,7 @@ export const CategoryPage = props => {
useEffect(() => {
getMeals();
// eslint-disable-next-line
}, []);
return (

View file

@ -7,6 +7,7 @@ export const CategoryListPage = props => {
useEffect(() => {
getCategories();
// eslint-disable-next-line
}, []);
return (

View file

@ -10,6 +10,7 @@ export const Meal = props => {
useEffect(() => {
idMeal === null ? getMeal() : getMeal(idMeal);
// eslint-disable-next-line
}, []);
const meal = props.meal.meals[0];

17
src/pages/Profile.jsx Normal file
View file

@ -0,0 +1,17 @@
import React from "react";
import { useAuth0 } from "../utils/auth0-spa";
export const Profile = () => {
const { loading, user } = useAuth0();
return loading || !user ? (
<div>Loading ... </div> // is catched by PrivateRoute
) : (
<div className="container">
<img className="responsive-img" src={user.picture} alt="Avatar" />
<h2>{user.name}</h2>
<p>{user.email}</p>
<code>{JSON.stringify(user, null, 2)}</code>
</div>
);
};

93
src/utils/auth0-spa.js Normal file
View file

@ -0,0 +1,93 @@
// adapted from: https://auth0.com/docs/quickstart/spa/react
import React, { useContext, useState, useEffect } from "react";
import createAuth0Client from "@auth0/auth0-spa-js";
const DEFAULT_REDIRECT_CALLBACK = () => {
window.history.replaceState({}, document.title, window.location.pathname);
};
export const Auth0Context = React.createContext();
export const useAuth0 = () => useContext(Auth0Context);
export const Auth0Provider = ({
children,
onRedirectCallBack = DEFAULT_REDIRECT_CALLBACK,
...initOptions
}) => {
const [isAuthenticated, setIsAuthenticated] = useState();
const [user, setUser] = useState();
const [auth0Client, setAuth0] = useState();
const [loading, setLoading] = useState(true);
const [popUpOpen, setPopUpOpen] = useState(false);
useEffect(() => {
const initAuth0 = async () => {
const auth0FromHook = await createAuth0Client(initOptions);
setAuth0(auth0FromHook);
if (
window.location.search.includes("code=") &&
window.location.search.includes("state=")
) {
const { appState } = await auth0FromHook.handleRedirectCallback();
onRedirectCallBack(appState);
}
const isAuthenticated = await auth0FromHook.isAuthenticated();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const user = await auth0FromHook.getUser();
setUser(user);
}
setLoading(false);
};
initAuth0();
// eslint-disable-next-line
}, []);
const loginWithPopUp = async (params = {}) => {
setPopUpOpen(true);
try {
await auth0Client.loginWithPopUp(params);
} catch (error) {
console.error(error);
} finally {
setPopUpOpen(false);
}
const user = auth0Client.getUser();
setUser(user);
setIsAuthenticated(true);
};
const handleRedirectCallback = async () => {
setLoading(true);
await auth0Client.handleRedirectCallback();
const user = auth0Client.getUser();
setLoading(false);
setIsAuthenticated(true);
setUser(user);
};
return (
<Auth0Context.Provider
value={{
isAuthenticated,
user,
loading,
popUpOpen,
loginWithPopUp,
handleRedirectCallback,
getIdTokenClaims: (...props) => auth0Client.getIdTokenClaims(...props),
loginWithRedirect: (...props) =>
auth0Client.loginWithRedirect(...props),
getTokenWithPopUp: (...props) =>
auth0Client.getTokenWithPopUp(...props),
logout: (...props) => auth0Client.logout(...props)
}}
>
{children}
</Auth0Context.Provider>
);
};

View file

@ -0,0 +1,5 @@
/* This is a dummy auth0 config file. Use https://auth0.com/ to create your own set of keys */
{
"domain": "{DOMAIN}",
"clientId": "{CLIENT_ID}"
}

View file

@ -1,111 +0,0 @@
import React, { useState, useEffect, useContext, createContext } from "react";
import queryString from "query-string";
import * as firebase from "firebase/app";
import "firebase/auth";
if (!firebase.apps.length) {
// Replace with your own Firebase credentials
firebase.initializeApp({
apiKey: "AIzaSyBkkFF0XhNZeWuDmOfEhsgdfX1VBG7WTas",
authDomain: "divjoy-demo.firebaseapp.com",
projectId: "divjoy-demo",
appID: "divjoy-demo"
});
}
const authContext = createContext();
// Provider component that wraps your app and makes auth object ...
// ... available to any child component that calls useAuth().
export function ProvideAuth({ children }) {
const auth = useProvideAuth();
return <authContext.Provider value={auth}>{children}</authContext.Provider>;
}
// Hook for child components to get the auth object ...
// ... update when it changes.
export const useAuth = () => {
return useContext(authContext);
};
// Provider hook that creates auth object and handles state
function useProvideAuth() {
const [user, setUser] = useState(null);
const signin = (email, password) => {
return firebase
.auth()
.signInWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signup = (email, password) => {
return firebase
.auth()
.createUserWithEmailAndPassword(email, password)
.then(response => {
setUser(response.user);
return response.user;
});
};
const signout = () => {
return firebase
.auth()
.signOut()
.then(() => {
setUser(false);
});
};
const sendPasswordResetEmail = email => {
return firebase
.auth()
.sendPasswordResetEmail(email)
.then(() => {
return true;
});
};
const confirmPasswordReset = (password, code) => {
// Get code from query string object
const resetCode = code || getFromQueryString("oobCode");
return firebase
.auth()
.confirmPasswordReset(resetCode, password)
.then(() => {
return true;
});
};
// Subscribe to user on mount
useEffect(() => {
const unsubscribe = firebase.auth().onAuthStateChanged(user => {
if (user) {
setUser(user);
} else {
setUser(false);
}
});
// Subscription unsubscribe function
return () => unsubscribe();
}, []);
return {
user,
signin,
signup,
signout,
sendPasswordResetEmail,
confirmPasswordReset
};
}
const getFromQueryString = key => {
return queryString.parse(window.location.search)[key];
};

2
src/utils/history.js Normal file
View file

@ -0,0 +1,2 @@
import { createBrowserHistory } from "history";
export default createBrowserHistory();