diff --git a/todo-android-1/build.gradle b/todo-android-1/build.gradle index 54dcf60cc..5c8b07d1c 100644 --- a/todo-android-1/build.gradle +++ b/todo-android-1/build.gradle @@ -8,7 +8,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:3.3.2' } } diff --git a/todo-web-1/package-lock.json b/todo-web-1/package-lock.json index aec12d965..21d2a52b0 100644 --- a/todo-web-1/package-lock.json +++ b/todo-web-1/package-lock.json @@ -1013,6 +1013,29 @@ "warning": "^3.0.0" } }, + "@react-bootstrap/react-popper": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@react-bootstrap/react-popper/-/react-popper-1.2.1.tgz", + "integrity": "sha512-4l3q7LcZEhrSkI4d3Ie3g4CdrXqqTexXX4PFT45CB0z5z2JUbaxgRwKNq7r5j2bLdVpZm+uvUGqxJw8d9vgbJQ==", + "requires": { + "babel-runtime": "6.x.x", + "create-react-context": "^0.2.1", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.5", + "warning": "^3.0.0" + } + }, + "@restart/context": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@restart/context/-/context-2.1.4.tgz", + "integrity": "sha512-INJYZQJP7g+IoDUh/475NlGiTeMfwTXUEr3tmRneckHIxNolGOW9CTq83S8cxq0CgJwwcMzMJFchxvlwe7Rk8Q==" + }, + "@restart/hooks": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.1.6.tgz", + "integrity": "sha512-eP1NykkdPlagH60YV+k2qGDCq3rkNtY6Sz0T2DVU1MQPa4pVRofIj+leFM2SLZLzy5nQFga4YnX7oM2d2LJRpA==" + }, "@rooks/use-timeout": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@rooks/use-timeout/-/use-timeout-1.2.0.tgz", @@ -7383,6 +7406,19 @@ "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==" }, + "history": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/history/-/history-4.9.0.tgz", + "integrity": "sha512-H2DkjCjXf0Op9OAr6nJ56fcRkTSNrUiv41vNJ6IswJjif6wlpZK0BTfFbi7qK9dXLSYZxkq5lBsj3vUjlYBYZA==", + "requires": { + "@babel/runtime": "^7.1.2", + "loose-envify": "^1.2.0", + "resolve-pathname": "^2.2.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0", + "value-equal": "^0.4.0" + } + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -9189,6 +9225,11 @@ "safe-buffer": "^5.0.1" } }, + "keycode": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", + "integrity": "sha1-PQr1bce4uOXLqNCpfxByBO7CKwQ=" + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -13042,6 +13083,15 @@ "react-is": "^16.8.1" } }, + "prop-types-extra": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/prop-types-extra/-/prop-types-extra-1.1.0.tgz", + "integrity": "sha512-QFyuDxvMipmIVKD2TwxLVPzMnO4e5oOf1vr3tJIomL8E7d0lr6phTHd5nkPhFIzTD1idBLLEPeylL9g+rrTzRg==", + "requires": { + "react-is": "^16.3.2", + "warning": "^3.0.0" + } + }, "property-information": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/property-information/-/property-information-5.0.1.tgz", @@ -13258,6 +13308,43 @@ } } }, + "react-bootstrap": { + "version": "1.0.0-beta.6", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-1.0.0-beta.6.tgz", + "integrity": "sha512-4RwXBCm45dHar1mMq7Wz8X9dI3b84R+Un696S+amgjePcxb6DqqqcxYla1FR7FJ+IIODr12HTjxu4LRVtIEsSQ==", + "requires": { + "@babel/runtime": "^7.3.4", + "@react-bootstrap/react-popper": "1.2.1", + "@restart/context": "^2.1.2", + "@restart/hooks": "^0.1.1", + "classnames": "^2.2.6", + "dom-helpers": "^3.4.0", + "invariant": "^2.2.3", + "keycode": "^2.1.2", + "popper.js": "^1.14.7", + "prop-types": "^15.7.2", + "prop-types-extra": "^1.1.0", + "react-overlays": "^1.2.0", + "react-transition-group": "^2.6.0", + "uncontrollable": "^6.1.0", + "warning": "^4.0.3" + }, + "dependencies": { + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, + "react-context-toolbox": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/react-context-toolbox/-/react-context-toolbox-2.0.2.tgz", + "integrity": "sha512-tY4j0imkYC3n5ZlYSgFkaw7fmlCp3IoQQ6DxpqeNHzcD0hf+6V+/HeJxviLUZ1Rv1Yn3N3xyO2EhkkZwHn0m1A==" + }, "react-dev-utils": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-8.0.0.tgz", @@ -13394,6 +13481,11 @@ "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-5.1.4.tgz", "integrity": "sha512-fp+U98OMZcnduQ+NSEiQa4s/XMsbp+5KlydmkbESOw4P69iWZ68ZMFM5a2BuE0FgqPBKApJyRuYHR95jM8lAmg==" }, + "react-facebook-login": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/react-facebook-login/-/react-facebook-login-4.1.1.tgz", + "integrity": "sha512-COnHEHlYGTKipz4963safFAK9PaNTcCiXfPXMS/yxo8El+/AJL5ye8kMJf23lKSSGGPgqFQuInskIHVqGqTvSw==" + }, "react-is": { "version": "16.8.5", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.8.5.tgz", @@ -13425,6 +13517,58 @@ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz", "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==" }, + "react-overlays": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-1.2.0.tgz", + "integrity": "sha512-i/FCV8wR6aRaI+Kz/dpJhOdyx+ah2tN1RhT9InPrexyC4uzf3N4bNayFTGtUeQVacj57j1Mqh1CwV60/5153Iw==", + "requires": { + "classnames": "^2.2.6", + "dom-helpers": "^3.4.0", + "prop-types": "^15.6.2", + "prop-types-extra": "^1.1.0", + "react-context-toolbox": "^2.0.2", + "react-popper": "^1.3.2", + "uncontrollable": "^6.0.0", + "warning": "^4.0.2" + }, + "dependencies": { + "create-react-context": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/create-react-context/-/create-react-context-0.2.2.tgz", + "integrity": "sha512-KkpaLARMhsTsgp0d2NA/R94F/eDLbhXERdIq3LvX2biCAXcDvHYoOqHfWCHf1+OLj+HKBotLG3KqaOOf+C1C+A==", + "requires": { + "fbjs": "^0.8.0", + "gud": "^1.0.0" + } + }, + "react-popper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-1.3.3.tgz", + "integrity": "sha512-ynMZBPkXONPc5K4P5yFWgZx5JGAUIP3pGGLNs58cfAPgK67olx7fmLp+AdpZ0+GoQ+ieFDa/z4cdV6u7sioH6w==", + "requires": { + "@babel/runtime": "^7.1.2", + "create-react-context": "<=0.2.2", + "popper.js": "^1.14.4", + "prop-types": "^15.6.1", + "typed-styles": "^0.0.7", + "warning": "^4.0.2" + } + }, + "typed-styles": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.7.tgz", + "integrity": "sha512-pzP0PWoZUhsECYjABgCGQlRGL1n7tOHsgwYv3oIiEpJwGhFTuty/YNeduxQYzXXa3Ge5BdT6sHYIQYpl4uJ+5Q==" + }, + "warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "requires": { + "loose-envify": "^1.0.0" + } + } + } + }, "react-popper": { "version": "0.10.4", "resolved": "https://registry.npmjs.org/react-popper/-/react-popper-0.10.4.tgz", @@ -13434,6 +13578,52 @@ "prop-types": "^15.6.1" } }, + "react-router": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-5.0.0.tgz", + "integrity": "sha512-6EQDakGdLG/it2x9EaCt9ZpEEPxnd0OCLBHQ1AcITAAx7nCnyvnzf76jKWG1s2/oJ7SSviUgfWHofdYljFexsA==", + "requires": { + "@babel/runtime": "^7.1.2", + "create-react-context": "^0.2.2", + "history": "^4.9.0", + "hoist-non-react-statics": "^3.1.0", + "loose-envify": "^1.3.1", + "path-to-regexp": "^1.7.0", + "prop-types": "^15.6.2", + "react-is": "^16.6.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "path-to-regexp": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", + "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "requires": { + "isarray": "0.0.1" + } + } + } + }, + "react-router-dom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-5.0.0.tgz", + "integrity": "sha512-wSpja5g9kh5dIteZT3tUoggjnsa+TPFHSMrpHXMpFsaHhQkm/JNVGh2jiF9Dkh4+duj4MKCkwO6H08u6inZYgQ==", + "requires": { + "@babel/runtime": "^7.1.2", + "history": "^4.9.0", + "loose-envify": "^1.3.1", + "prop-types": "^15.6.2", + "react-router": "5.0.0", + "tiny-invariant": "^1.0.2", + "tiny-warning": "^1.0.0" + } + }, "react-scripts": { "version": "2.1.8", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-2.1.8.tgz", @@ -14146,6 +14336,11 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, + "resolve-pathname": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-2.2.0.tgz", + "integrity": "sha512-bAFz9ld18RzJfddgrO2e/0S2O81710++chRMUxHjXOYKF6jTAMrUNZrEZ1PvV0zlhfjidm08iRPdTLPno1FuRg==" + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -15609,6 +15804,16 @@ "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=" }, + "tiny-invariant": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.0.4.tgz", + "integrity": "sha512-lMhRd/djQJ3MoaHEBrw8e2/uM4rs9YMNk0iOr8rHQ0QdbM7D4l0gFl3szKdeixrlyfm9Zqi4dxHCM2qVG8ND5g==" + }, + "tiny-warning": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.2.tgz", + "integrity": "sha512-rru86D9CpQRLvsFG5XFdy0KdLAvjdQDyZCsRcuu60WtzFylDM3eAWSxEVz5kzL2Gp544XiUvPbVKtOA/txLi9Q==" + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -15760,6 +15965,11 @@ "mime-types": "~2.1.18" } }, + "typed-styles": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/typed-styles/-/typed-styles-0.0.5.tgz", + "integrity": "sha512-ht+rEe5UsdEBAa3gr64+QjUOqjOLJfWLvl5HZR5Ev9uo/OnD3p43wPeFSB1hNFc13GXQF/JU1Bn0YHLUqBRIlw==" + }, "typedarray": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", @@ -15792,6 +16002,14 @@ } } }, + "uncontrollable": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-6.1.0.tgz", + "integrity": "sha512-2TzEm0pLKauMBZfAZXsgQvLpZHEp95891frCZdGDrSG7dWYaIQhedwLAzi0X8pR8KHNqlmuYEb2cEgbQzr050A==", + "requires": { + "invariant": "^2.2.4" + } + }, "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", @@ -16054,6 +16272,11 @@ "spdx-expression-parse": "^3.0.0" } }, + "value-equal": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/value-equal/-/value-equal-0.4.0.tgz", + "integrity": "sha512-x+cYdNnaA3CxvMaTX0INdTCN8m8aF2uY9BvEqmxuYp8bL09cs/kWVQPVGcA35fMktdOsP69IgU7wFj/61dJHEw==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", diff --git a/todo-web-1/package.json b/todo-web-1/package.json index 50b1e38c0..f5287d7c1 100755 --- a/todo-web-1/package.json +++ b/todo-web-1/package.json @@ -18,9 +18,12 @@ "leaflet.awesome-markers": "2.0.5", "mongodb-stitch-browser-sdk": "4.3.2", "react": "16.8.3", + "react-bootstrap": "^1.0.0-beta.6", "react-dom": "16.8.3", "react-error-boundary": "1.2.3", + "react-facebook-login": "^4.1.1", "react-leaflet": "2.2.1", + "react-router-dom": "^5.0.0", "react-scripts": "2.1.8", "reactstrap": "7.1.0" }, diff --git a/todo-web-1/public/index.html b/todo-web-1/public/index.html index 456bddbdc..3a9650cf4 100755 --- a/todo-web-1/public/index.html +++ b/todo-web-1/public/index.html @@ -22,7 +22,7 @@ --> - React App + Stitch Todo App Tutorial diff --git a/todo-web-1/public/manifest.json b/todo-web-1/public/manifest.json new file mode 100644 index 000000000..0a74c22be --- /dev/null +++ b/todo-web-1/public/manifest.json @@ -0,0 +1,15 @@ +{ + "short_name": "Stitch Todo App Tutorial", + "name": "Stitch Todo App Tutorial", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" + } \ No newline at end of file diff --git a/todo-web-1/src/components/App.js b/todo-web-1/src/components/App.js index 45474222d..6f00c2bb1 100755 --- a/todo-web-1/src/components/App.js +++ b/todo-web-1/src/components/App.js @@ -1,9 +1,12 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import styled from "@emotion/styled"; import TodoList from "./TodoList"; import Banner from "./Banner"; import Navbar from "./Navbar"; -import PropTypes from 'prop-types'; +import PropTypes from "prop-types"; +import { BrowserRouter as Router, Route, Link } from "react-router-dom"; +import ConfirmUser from "./ConfirmUser"; +import ConfirmUserAPI from "./ConfirmUserAPI"; const AppLayout = styled.div` display: grid; @@ -18,16 +21,33 @@ const AppLayout = styled.div` `; App.propTypes = { - children: PropTypes.node, + children: PropTypes.node }; export default function App(props) { + return ( - - - - - - + + + + + + + + + + + + + ); } diff --git a/todo-web-1/src/components/ConfirmUser.js b/todo-web-1/src/components/ConfirmUser.js new file mode 100644 index 000000000..0ea1807fc --- /dev/null +++ b/todo-web-1/src/components/ConfirmUser.js @@ -0,0 +1,89 @@ +import React, { Component } from "react"; +import { + Button, + FormGroup, + FormControl, + FormLabel +} from "react-bootstrap"; +import { Card, CardBody } from "reactstrap"; +import styled from "@emotion/styled"; +import confirmUser from "../stitch/authentication"; + +const LoginCard = styled(Card)` + background-color: #383a3f !important; + background-color: #3e4348 !important; + background-color: #1f2124 !important; + background-color: #011627 !important; +`; +export default class Confirm extends Component { + constructor(props) { + super(props); + + this.state = { + token: "", + tokenId: "", + message: "" + }; + } + + validateForm() { + return this.state.token.length > 0 && this.state.tokenId.length > 0; + } + + handleChange = event => { + this.setState({ + [event.target.id]: event.target.value + }); + }; + + handleSubmit = event => { + event.preventDefault(); + console.log(this.state.token); + + confirmUser(this.state.token, this.state.tokenId) + .then(() => { + this.setState({message:"Successfully confirmed user's email"}); + }) + .catch(err => { + console.log("Error confirming new user:", err); + this.setState({message:err.message}); + }); + }; + + render() { + return ( +
+ + +
+ + Token + + + + TokenId + + + +
+
+ {this.state.message} +
+
+
+
+
+ ); + } +} diff --git a/todo-web-1/src/components/ConfirmUserAPI.js b/todo-web-1/src/components/ConfirmUserAPI.js new file mode 100644 index 000000000..e9e8c13cd --- /dev/null +++ b/todo-web-1/src/components/ConfirmUserAPI.js @@ -0,0 +1,33 @@ +import React, { Component } from "react"; +import confirmUser from "../stitch/authentication"; + + +export default class ConfirmAPI extends Component { + constructor(props) { + super(props); + + this.state = { + token: "", + tokenId: "" + }; + } + componentDidMount() { + let location = window.location; + let params = new URLSearchParams(location.search); + let token = params.get("token"); + let tokenId = params.get("tokenId"); + + confirmUser(token, tokenId) + .then(() => { + this.setState({ message: "Successfully confirmation user's email" }); + }) + .catch(err => { + console.log("Error confirming new user:", err); + this.setState({ message: err.message }); + }); + } + + render() { + return
; + } +} diff --git a/todo-web-1/src/components/LoginAnon.js b/todo-web-1/src/components/LoginAnon.js index a4b3e8ad3..83f1e9073 100644 --- a/todo-web-1/src/components/LoginAnon.js +++ b/todo-web-1/src/components/LoginAnon.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React from "react"; import styled from "@emotion/styled"; import ErrorBoundary from "react-error-boundary"; import { @@ -6,14 +6,12 @@ import { CardBody, Button, Form, - FormGroup, - Label, - Input, - FormText, } from "reactstrap"; import Banner from "./Banner"; import PropTypes from 'prop-types'; - +import RegisterUser from "./RegisterUser"; +import Login from "./LoginUser"; +import LoginFacebook from "./LoginFacebook"; const LoginLayout = styled.div` display: grid; @@ -38,6 +36,7 @@ const LoginContent = styled.div` grid-area: content; position: absolute; top: 150px; + left: 190px; `; @@ -47,6 +46,12 @@ export default function LoginAnon(props) { + + or + + or + + or diff --git a/todo-web-1/src/components/LoginFacebook.js b/todo-web-1/src/components/LoginFacebook.js new file mode 100644 index 000000000..7d9c18baa --- /dev/null +++ b/todo-web-1/src/components/LoginFacebook.js @@ -0,0 +1,34 @@ +import React, { Component } from "react"; +import { Button } from "react-bootstrap"; +import { app } from "./../stitch/stitch"; +import { FacebookRedirectCredential } from "mongodb-stitch-browser-sdk"; +export default class Login extends Component { + async componentDidMount() { + console.log("Component did mount"); + + if (app.auth.hasRedirectResult()) { + await app.auth.handleRedirectResult(); + return; + } + + if (!app.auth.isLoggedIn) { + const credential = new FacebookRedirectCredential(); + await app.auth.loginWithRedirect(credential); + return; + } + } + + handleSubmit = event => {}; + + render() { + return ( +
+
+ +
+
+ ); + } +} diff --git a/todo-web-1/src/components/LoginUser.js b/todo-web-1/src/components/LoginUser.js new file mode 100644 index 000000000..5450f4604 --- /dev/null +++ b/todo-web-1/src/components/LoginUser.js @@ -0,0 +1,73 @@ +import React, { Component } from "react"; +import { Button, FormGroup, FormControl, FormLabel } from "react-bootstrap"; +import { Card, CardBody } from "reactstrap"; +import styled from "@emotion/styled"; +import { loginEmailPasswordUser } from "../stitch/authentication"; + +const LoginCard = styled(Card)` + background-color: #383a3f !important; + background-color: #3e4348 !important; + background-color: #1f2124 !important; + background-color: #011627 !important; +`; +export default class Login extends Component { + constructor(props) { + super(props); + + this.state = { + email: "", + password: "" + }; + } + + validateForm() { + return this.state.email.length > 0 && this.state.password.length > 0; + } + + handleChange = event => { + this.setState({ + [event.target.id]: event.target.value + }); + }; + + handleSubmit = event => { + event.preventDefault(); + console.log(this.state.email); + loginEmailPasswordUser(this.state.email, this.state.password).then(() => { + window.location.reload(); + }); + }; + + render() { + return ( +
+ + +
+ + Email + + + + Password + + + +
+
+
+
+ ); + } +} diff --git a/todo-web-1/src/components/RegisterUser.js b/todo-web-1/src/components/RegisterUser.js new file mode 100644 index 000000000..36faf56c9 --- /dev/null +++ b/todo-web-1/src/components/RegisterUser.js @@ -0,0 +1,110 @@ +import React, { Component } from "react"; +import { + Button, + FormGroup, + FormControl, + FormLabel +} from "react-bootstrap"; +import { Card, CardBody } from "reactstrap"; +import styled from "@emotion/styled"; +import { registerUser, resendConfirmationEmail } from "../stitch/authentication"; + +const LoginCard = styled(Card)` + background-color: #383a3f !important; + background-color: #3e4348 !important; + background-color: #1f2124 !important; + background-color: #011627 !important; +`; + +export default class RegisterUser extends Component { + constructor(props) { + super(props); + + this.handleResendEmail = this.handleResendEmail.bind(this); + + this.state = { + email: "", + password: "", + message: "" + }; + } + + validateForm() { + return this.state.email.length > 0 && this.state.password.length > 0; + } + + validateEmail() { + return this.state.email.length > 0; + } + + handleChange = event => { + this.setState({ + [event.target.id]: event.target.value + }); + }; + + handleSubmit = event => { + event.preventDefault(); + console.log(this.state.email); + + registerUser(this.state.email, this.state.password) + .then(() => { + this.setState({message:"Successfully sent account confirmation email"}); + }) + .catch(err => { + console.log("Error registering new user:", err.message); + this.setState({message:err.message}); + }); + }; + + handleResendEmail() { + resendConfirmationEmail(this.state.email) + .then(() => { + this.setState({message:"Successfully resent account confirmation email"}); + }) + .catch(err => { + console.log("Error resending account confirmation email:", err.message); + this.setState({message:err.message}); + }); +} + + render() { + return ( +
+ + +
+ + Email + + + + Password + + + + +
+
+ {this.state.message} +
+
+
+
+
+ ); + } +} diff --git a/todo-web-1/src/components/TodoItem.js b/todo-web-1/src/components/TodoItem.js index b781a84f4..899045478 100644 --- a/todo-web-1/src/components/TodoItem.js +++ b/todo-web-1/src/components/TodoItem.js @@ -1,5 +1,5 @@ import React from "react"; -import {app, items} from "./../stitch"; +import {items} from "./../stitch"; import PropTypes from 'prop-types'; var TodoItem = class extends React.Component { @@ -8,7 +8,7 @@ var TodoItem = class extends React.Component { items .updateOne( { _id: this.props.item._id }, - { $set: { checked: !this.props.item.checked } } + { $set: { checked: !this.props.item.checked }, $inc: { "__stitch_sync_version.v": 1 } } ) .then(() => this.props.onChange()); } diff --git a/todo-web-1/src/components/TodoList.js b/todo-web-1/src/components/TodoList.js index 3fd748653..21a010814 100644 --- a/todo-web-1/src/components/TodoList.js +++ b/todo-web-1/src/components/TodoList.js @@ -1,102 +1,115 @@ import React from "react"; import TodoItem from "./TodoItem"; -import {app, items} from "./../stitch"; +import { app, items } from "./../stitch"; var TodoList = class extends React.Component { - - loadList() { - console.log(app.auth.user); - let obj = this; - items.find({}, {limit: 1000}).asArray().then(docs => { - obj.setState({ items: docs, requestPending: false }); + loadList() { + console.log(app.auth.user); + let obj = this; + items + .find({}, { limit: 1000 }) + .asArray() + .then(docs => { + obj.setState({ items: docs, requestPending: false }); }); - } + } -constructor(props) { - super(props); + constructor(props) { + super(props); - this.state = { + this.state = { items: [] - }; -} - + }; + } -checkHandler(id, status) { - items.updateOne({ _id: id }, { $set: { checked: status } }).then(() => { - this.loadList(); - }, { rule: "checked" }); -} + // Update MongoDB + checkHandler(id, status) { + items + .updateOne( + { _id: id }, + { $set: { checked: status } } + ) + .then( + () => { + this.loadList(); + }, + { rule: "checked" } + ); + } -componentDidMount() { - this.loadList(); -} + componentDidMount() { + this.loadList(); + } -addItem(event) { - if (event.keyCode != 13) { + addItem(event) { + if (event.keyCode !== 13) { return; - } - this.setState({ requestPending: true }); - items.insertOne({ text: event.target.value, owner_id: app.auth.user.id }) + } + this.setState({ requestPending: true }); + items + .insertOne({ text: event.target.value, owner_id: app.auth.user.id }) .then(() => { - this._newitem.value = ""; - this.loadList(); + this._newitem.value = ""; + this.loadList(); }); -} + } -clear() { - this.setState({ requestPending: true }); - items.deleteMany({ checked: true }).then(() => { + clear() { + this.setState({ requestPending: true }); + items.deleteMany({ checked: true }).then(() => { this.loadList(); - }); -} + }); + } -setPending() { - this.setState({ requestPending: true }); -} + setPending() { + this.setState({ requestPending: true }); + } -render() { - let foo = ( + render() { + let foo = (
-
- + { - this._newitem = n; + this._newitem = n; }} onKeyDown={e => this.addItem(e)} - /> - {this.state.items.filter(x => x.checked).length > 0 - ?
this.clear()} + /> + {this.state.items.filter(x => x.checked).length > 0 ? ( +
this.clear()} > - delete selected item(s) + delete selected item(s)
- : null} -
-
    - {this.state.items.length == 0 - ?
    empty list.
    - : this.state.items.map(item => { - return ( - +
      + {this.state.items.length === 0 ? ( +
      empty list.
      + ) : ( + this.state.items.map(item => { + return ( + this.loadList()} onStartChange={() => this.setPending()} - /> - ); - })} -
    + /> + ); + }) + )} +
- ); - return foo; -} + ); + return foo; + } }; TodoList.displayName = "TodoList"; -export default TodoList; \ No newline at end of file +export default TodoList; diff --git a/todo-web-1/src/index.js b/todo-web-1/src/index.js index bea30d458..280123f93 100644 --- a/todo-web-1/src/index.js +++ b/todo-web-1/src/index.js @@ -1,10 +1,6 @@ import React from "react"; import ReactDOM from "react-dom"; -import {app, - isLoggedIn, - loginAnonymous, - logoutUser, -} from "./stitch"; +import { app, isLoggedIn, loginAnonymous, logoutUser } from "./stitch"; import LoginAnon from "./components/LoginAnon"; import App from "./components/App"; import "bootstrap/dist/css/bootstrap.min.css"; @@ -15,7 +11,9 @@ function MyApp(props) { return isLoggedIn() ? ( logoutUser(app.currentUser)} /> ) : ( - +
+ +
); } diff --git a/todo-web-1/src/stitch/authentication.js b/todo-web-1/src/stitch/authentication.js index 030e16e48..2e6feca01 100644 --- a/todo-web-1/src/stitch/authentication.js +++ b/todo-web-1/src/stitch/authentication.js @@ -1,11 +1,13 @@ -import { useState, useEffect } from "react"; -import { UserPasswordCredential, AnonymousCredential } from "mongodb-stitch-browser-sdk"; -import {app} from "./stitch.js"; +import { + UserPasswordCredential, + AnonymousCredential +} from "mongodb-stitch-browser-sdk"; +import { app, emailPassClient } from "./stitch.js"; // Log in a user with the specified email and password // Note: The user must already be registered with the Stitch app. // See https://docs.mongodb.com/stitch/authentication/userpass/#create-a-new-user-account -export function loginEmailPasswordUser({ email, password }) { +export function loginEmailPasswordUser(email, password) { return app.auth .loginWithCredential(new UserPasswordCredential(email, password)) .then(stitchUser => { @@ -14,7 +16,25 @@ export function loginEmailPasswordUser({ email, password }) { }); } -// Log in a user anonymously. +// Register a user with the specified email and password +export function registerUser(email, password) { + return emailPassClient.registerWithEmail(email, password).then(() => { + console.log("Successfully sent account confirmation email"); + }); +} + +// Confirm the user's email/password account +export default function confirmUser(token, tokenId) { + return emailPassClient.confirmUser(token, tokenId); +} + +export function resendConfirmationEmail(email) { + return emailPassClient.resendConfirmationEmail(email).then(() => { + console.log("Successfully sent confirmation email"); + }) +} + +// Log in a user anonymously. // Note: When the user logs out, all data is lost. // See https://docs.mongodb.com/stitch/authentication/anonymous/ export function loginAnonymous() { @@ -42,6 +62,5 @@ export function logoutUser(stitchUser) { } export function isLoggedIn() { - return app.auth.isLoggedIn; + return app.auth.isLoggedIn; } -