diff --git a/package-lock.json b/package-lock.json index 6a3a765..f6cc710 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,28 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@auth0/auth0-react": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-react/-/auth0-react-1.0.0.tgz", + "integrity": "sha512-AaGftJFXYsJNEHZq/PNNr5HVSOQCVjstBF2pD3gAwOrgjKO7fYoeNgKKQOcTLm/ge5vAkEBGgSBzWBj2SgZ38w==", + "requires": { + "@auth0/auth0-spa-js": "^1.9.0" + } + }, + "@auth0/auth0-spa-js": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@auth0/auth0-spa-js/-/auth0-spa-js-1.10.0.tgz", + "integrity": "sha512-EUpnGK6GnKUiHnGbP+Y+yKD+FZ8/kEkjPeLxQi0gCGJkvTSLnKd/IbPJLUzyzXFTqDCw1ZkTu24/Uw5B3Ou2JQ==", + "requires": { + "abortcontroller-polyfill": "^1.4.0", + "browser-tabs-lock": "^1.2.8", + "core-js": "^3.6.4", + "es-cookie": "^1.3.2", + "fast-text-encoding": "^1.0.1", + "promise-polyfill": "^8.1.3", + "unfetch": "^4.1.0" + } + }, "@babel/code-frame": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz", @@ -2197,6 +2219,11 @@ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" }, + "abortcontroller-polyfill": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.4.0.tgz", + "integrity": "sha512-3ZFfCRfDzx3GFjO6RAkYx81lPGpUS20ISxux9gLxuKnqafNcFQo59+IoZqpO2WvQlyc287B62HDnDdNYRmlvWA==" + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -3249,6 +3276,11 @@ } } }, + "browser-tabs-lock": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/browser-tabs-lock/-/browser-tabs-lock-1.2.8.tgz", + "integrity": "sha512-Xrj33YUTltPDoGrD1KnaAn5ZuxnnlJFcIW9srVTPHbMNPd9MlcnBCWaGV0STlvGKu8Ok0ad5qxyx5sIwFTr/Ig==" + }, "browserify-aes": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", @@ -4961,6 +4993,11 @@ "string.prototype.trimstart": "^1.0.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", @@ -5767,6 +5804,11 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" }, + "fast-text-encoding": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.3.tgz", + "integrity": "sha512-dtm4QZH9nZtcDt8qJiOH9fcQd1NAgi+K1O2DbE6GG1PPCK/BWfOH3idCTRQ4ImXRUOyopDEgDEnVEE7Y/2Wrig==" + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -10462,6 +10504,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.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.2.tgz", @@ -12995,6 +13042,11 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==" }, + "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", diff --git a/package.json b/package.json index 16a88a5..a0d942d 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "private": true, "dependencies": { + "@auth0/auth0-react": "^1.0.0", "@material-ui/core": "^4.10.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.56", diff --git a/src/App.tsx b/src/App.tsx index ffacc23..3cc9d17 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,10 @@ -import React from 'react'; +import React, { ComponentType } from 'react'; import WordBoard from './training/WordBoard'; import { - BrowserRouter as Router, + Router, Switch, - Route + Route, + RouteProps } from "react-router-dom"; import { ScrabbleLetter } from './components/Tile'; import Header from './components/Header'; @@ -21,6 +22,8 @@ import Rereference from './reference/Reference'; import { WordList } from './reference/wordLists'; import { Stems } from './training/Stems/Stem'; import StemList from './training/Stems/StemList'; +import { Auth0Provider, withAuthenticationRequired } from "@auth0/auth0-react"; +import { createBrowserHistory } from 'history'; export const allLetters: ScrabbleLetter[] = [ "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"] @@ -43,45 +46,69 @@ class RoutePaths { WordCheck = `${this.Reference}/WordCheck`; WordList = `${this.Reference}/WordList`; StemList = `${this.Training}/bingostems`; + Login = "/login" Stem = (word: string, letter: string) => `${this.StemList}/${word}/${letter}`; - TwoLetterWords = (letter: ScrabbleLetter) => `${this.Training}/2LetterWords/${letter}`; - ThreeLetterWords = (letter: ScrabbleLetter) => `${this.Training}/3LetterWords/${letter}`; + SmallWordsTraining = (letter: ScrabbleLetter, numberOfLetters: number) => `${this.Training}/${numberOfLetters}LetterWords/${letter}`; +} +interface loginProps { + url: string } + +const onRedirectCallback = (appState: any) => { + // Use the router's history module to replace the url + console.table(appState) + history.replace(appState?.returnTo || window.location.pathname); +}; + + export const Routes = new RoutePaths() +const ProtectedRoute = ({ component, ...args }: RouteProps) => { + return )} {...args} /> +}; + +export const history = createBrowserHistory(); + function App() { const classes = useStyles(); return ( <> - - - - -
-
- -
- - - } /> - } /> - } /> - - - - - - - - - -
-
-
-
-
+ + + + + +
+
+ +
+ + + + + + + + + + + + + + +
+
+
+
+
+
); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 51dfca2..b48d676 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,14 +1,16 @@ import React from 'react'; -import { Toolbar, AppBar, IconButton, Typography, Theme } from '@material-ui/core'; +import { Toolbar, AppBar, IconButton, Typography, Theme, Button, Menu, MenuItem } from '@material-ui/core'; import { makeStyles } from '@material-ui/core/styles'; import MenuIcon from '@material-ui/icons/Menu'; import Tile from './Tile'; import { useNavControl } from '../navbar/navbar'; +import { useAuth0 } from '@auth0/auth0-react'; +import AccountCircle from '@material-ui/icons/AccountCircle'; const useStyles = makeStyles((theme: Theme) => ({ appBar: { boxShadow: "none", - zIndex: theme.zIndex.drawer + 300, + zIndex: theme.zIndex.drawer + 1, }, title: { color: theme.palette.common.white, @@ -16,22 +18,73 @@ const useStyles = makeStyles((theme: Theme) => ({ }, tile: { transform: "rotate(8deg)", - boxShadow: "none" - } + boxShadow: "none", + alignSelf: "center" + }, + logo: { + flexGrow: 1, + display: "flex" + }, })); const Header = () => { const classes = useStyles(); + const { loginWithRedirect, isAuthenticated, logout } = useAuth0(); const [, toggleOpen] = useNavControl() + const [anchorEl, setAnchorEl] = React.useState(null); + const open = Boolean(anchorEl); + + const appState = { + returnTo: window.location.pathname + } + + const handleMenu = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + + const handleClose = () => { + setAnchorEl(null); + }; return ( { } - crabble Trainer +
crabble Trainer
+ { !isAuthenticated ? + + :
+ + + + + logout()}>Logout + +
+}
); } diff --git a/src/components/drawer.tsx b/src/components/drawer.tsx index d227606..ed7f198 100644 --- a/src/components/drawer.tsx +++ b/src/components/drawer.tsx @@ -70,11 +70,13 @@ export const SideDrawer: FunctionComponent = ({ children }) => { <>
@@ -95,14 +97,14 @@ export const SideDrawer: FunctionComponent = ({ children }) => { {itemOpen ? : } - () => history.push(Routes.TwoLetterWords(l))} size="Small" /> + () => history.push(Routes.SmallWordsTraining(l, 2))} size="Small" /> childNavEvent(e, () => setItemOpen2(!itemOpen2))}> {itemOpen2 ? : } - () => history.push(Routes.ThreeLetterWords(l))} size="Small" /> + () => history.push(Routes.SmallWordsTraining(l, 3))} size="Small" /> history.push(Routes.StemList)}> diff --git a/src/training/Training.tsx b/src/training/Training.tsx index 1d9575f..3a10d03 100644 --- a/src/training/Training.tsx +++ b/src/training/Training.tsx @@ -28,11 +28,11 @@ export const Training = () => { 2} - content = { () => history.push(Routes.TwoLetterWords(l))} />}/> + content = { () => history.push(Routes.SmallWordsTraining(l, 2))} />}/> 3} - content = { () => history.push(Routes.ThreeLetterWords(l))} />} /> + content = { () => history.push(Routes.SmallWordsTraining(l, 3))} />} /> S} diff --git a/src/training/WordBoard.tsx b/src/training/WordBoard.tsx index c5c8cee..a9ff437 100644 --- a/src/training/WordBoard.tsx +++ b/src/training/WordBoard.tsx @@ -3,7 +3,7 @@ import React, { useEffect, useState } from 'react'; import Word from '../components/Word'; import { makeStyles, IconButton } from '@material-ui/core'; import DoneAllIcon from '@material-ui/icons/DoneAll'; -import { RouteComponentProps, useHistory } from 'react-router-dom'; +import { useHistory, useParams } from 'react-router-dom'; import { useWordList } from '../contextProviders/dictionaryProvider'; import { allLetters, Routes } from '../App'; import FitnessCenterIcon from '@material-ui/icons/FitnessCenter'; @@ -12,17 +12,14 @@ import ScrabbleCard from '../components/scrabbleCard'; import LetterPagination from '../components/letterPagination'; import Results from '../components/results'; -interface WordBoardProps extends RouteComponentProps { - numberOfLetters: number -} - interface IMatchParams { letter: ScrabbleLetter + numberOfLetters: string } -const numberMap: { [key: number]: string } = { - 2: "Two", - 3: "Three" +const numberMap: { [key: string]: string } = { + "2": "Two", + "3": "Three" } const useStyles = makeStyles((theme) => ({ @@ -49,21 +46,23 @@ const useStyles = makeStyles((theme) => ({ -const WordBoard = (props: WordBoardProps) => { +const WordBoard = ( ) => { + const params = useParams(); + const history = useHistory(); - const firstLetter = props.match.params.letter.toUpperCase(); //regex doesn't seem to work + const firstLetter = params.letter.toUpperCase(); //regex doesn't seem to work const [letters, setLetters] = useState>(new Set(firstLetter)) const [selectedWords, setSelectedWords] = useState>(new Set()) const [showResults, setShowResults] = useState(false); - const validWords = useWordList(firstLetter, props.numberOfLetters) - const paginationAction = props.numberOfLetters === 2 ? ((l: string) => history.push(Routes.TwoLetterWords(l as ScrabbleLetter))) : ((l: string) => history.push(Routes.ThreeLetterWords(l as ScrabbleLetter))) + const validWords = useWordList(firstLetter, Number(params.numberOfLetters)) + const paginationAction = (l: string) => history.push(Routes.SmallWordsTraining(l as ScrabbleLetter, Number(params.numberOfLetters))) useEffect(() => { if (validWords.state === "Loaded") { let l = [[firstLetter as ScrabbleLetter]] - for (let i = 1; i < props.numberOfLetters; i++) { + for (let i = 1; i < Number(params.numberOfLetters); i++) { l = l.flatMap(x => allLetters.map(y => [...x, y])) } @@ -92,7 +91,7 @@ const WordBoard = (props: WordBoardProps) => { setSelectedWords(new Set()); setShowResults(false); } - }, [firstLetter, props.numberOfLetters, validWords]) + }, [firstLetter, params.numberOfLetters, validWords]) const numberCorrect = [...selectedWords].filter(x => (validWords.state === "Loaded") ? validWords.result.has(x) : false).length const numberIncorrect = [...selectedWords].filter(x => (validWords.state === "Loaded") ? !validWords.result.has(x) : false).length @@ -105,7 +104,7 @@ const WordBoard = (props: WordBoardProps) => { return (<>Find the word that begins with the letter {firstLetter}) } if (numberOfWords === 0) { - return (<>There are no {props.numberOfLetters} letter {firstLetter} words!) + return (<>There are no {params.numberOfLetters} letter {firstLetter} words!) } return <>Find {totalCorrect} words that begin with the letter {firstLetter} @@ -128,7 +127,7 @@ const WordBoard = (props: WordBoardProps) => { const avatar = - const title = `${numberMap[props.numberOfLetters]} letter words` + const title = `${numberMap[params.numberOfLetters]} letter words` const content = <>
{[...letters].sort().map((l, idx) => @@ -153,4 +152,5 @@ const WordBoard = (props: WordBoardProps) => { />) } -export default WordBoard \ No newline at end of file +export default WordBoard +